View Javadoc

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  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          EditingContextFlushChangesRunLoopOrdering = 300000;
92  
93      private static NSSelector runLaterSelector = 
94          new NSSelector( "flushRecentChanges",
95              new Class[] { Object.class } );
96  
97      private static EOObjectStore defaultParentObjectStore = null;
98      private static double defaultFetchTimestampLag = 0;
99      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     static protected String MessageChangeConflict =
136         "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         this( defaultParentObjectStore() );
146     }
147 
148     /***
149     * Creates a new editing context that uses the specified
150     * object store as its parent object store.
151     */
152     public EOEditingContext( EOObjectStore anObjectStore )
153     {
154         if ( anObjectStore == null ) 
155         {
156             throw new IllegalArgumentException(
157                 "A parent object store must be specified." );
158         }
159         
160         parentStore = anObjectStore;
161         delegate = null;
162         messageHandler = null;
163         editorSet = new LinkedList();
164         fetchTimestamp = 0;
165         lockBeforeModify = false;
166         propagateDeletesAfterEvent = true;
167         stopValidationAfterError = true;
168         insertedObjects = new NSMutableArray();
169         insertedObjectsBuffer = new NSMutableArray();
170         insertedObjectsProxy = NSArray.arrayBackedByList( insertedObjects );
171         updatedObjects = new NSMutableArray();
172         updatedObjectsBuffer = new NSMutableArray();
173         updatedObjectsProxy = NSArray.arrayBackedByList( updatedObjects );
174         deletedObjects = new NSMutableArray();
175         deletedObjectsBuffer = new NSMutableArray();
176         deletedObjectsProxy = NSArray.arrayBackedByList( deletedObjects );
177         deletedIDsBuffer = new NSMutableArray();
178         invalidatedObjectsBuffer = new NSMutableArray();
179         invalidatedIDsBuffer = new NSMutableArray();
180         
181         if ( instancesRetainRegisteredObjects() )
182         {
183             registrar = new Registrar( this );
184         }
185         else
186         {
187             registrar = new WeakRegistrar( this );
188         }
189         
190         ignoreChanges = false;
191         willRunLater = false;
192         isInvalidating = false;
193 
194         // create undo manager
195         //TODO: this should be NSUndoManager
196 //        undoManager = new UndoManager();
197 
198         // register for notifications
199         NSSelector handleNotification = 
200             new NSSelector( "handleNotification",
201                 new Class[] { NSNotification.class } );
202         // any from parent store
203         NSNotificationCenter.defaultCenter().addObserver(
204             this, handleNotification, null, parentStore );
205         // global id change from any
206         NSNotificationCenter.defaultCenter().addObserver(
207             this, handleNotification, EOGlobalID.GlobalIDChangedNotification, null );
208 //new net.wotonomy.ui.swing.NotificationInspector( null, parentStore );                
209     }
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         if ( anEditor == null ) return;
219         editorSet.add( new WeakReference( anEditor ) );
220     }
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         NSArray result = null;
240         Object source = registrar.objectForGlobalID( aGlobalID );
241         
242         // if not registered in our context
243         if ( source == null )
244         {
245             // get the object registered into our context
246             result = parentStore.arrayFaultWithSourceGlobalID( 
247                 aGlobalID, aRelationshipKey, this );
248         }
249         else // source is registered in our context
250         {
251             // get existing value
252             Object value;
253             if ( source instanceof EOKeyValueCoding )
254             {
255                 value = ((EOKeyValueCoding)source).storedValueForKey( 
256                     aRelationshipKey );
257             }
258             else // handle directly
259             {
260                 value = EOKeyValueCodingSupport.storedValueForKey( 
261                     source, aRelationshipKey );
262             }
263             
264             if ( value == null )
265             {
266                 // do the same as if the source was null
267                 result = parentStore.arrayFaultWithSourceGlobalID( 
268                     aGlobalID, aRelationshipKey, this );
269             }
270             else
271             if ( value instanceof NSArray )
272             {
273                 result = (NSArray) value;
274             }
275             else // not NSArray
276             if ( value instanceof Collection )
277             {
278                 // convert to NSArray
279                 result = new NSArray( (Collection) value );
280             }
281             else
282             {
283                 throw new WotonomyException( 
284                     "Relationship key did not return a collection: " 
285                     + aGlobalID + " : " + aRelationshipKey );   
286             }
287         }
288         
289         // if our context is not the specified context
290         if ( aContext != this ) 
291         {
292             result = (NSArray) clone( this, result, aContext );
293         }
294         
295         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         byte[] snapshot = (byte[])
307             registrar.getCommitSnapshot( anObject );
308         if ( snapshot == null )
309         {
310             // this object not modified: take a current snapshot
311             snapshot = takeSnapshot( anObject );
312         }                
313         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         byte[] result = (byte[])
325             registrar.getCurrentSnapshot( anObject );
326         if ( result == null )
327         {
328             return committedSnapshotForObject( anObject );   
329         }
330         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         if ( delegate == null ) return null;
340         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         willChange();
352 
353 		int i;
354 		// remove from added objects if necessary
355 		i = insertedObjects.indexOfIdenticalObject( anObject );
356 		if ( i != NSArray.NotFound )
357 		{
358 			insertedObjects.removeObjectAtIndex( i );
359                         
360             // if in the inserted objects buffer
361             int index = insertedObjectsBuffer.indexOfIdenticalObject( anObject );
362             if ( index != NSArray.NotFound )
363             {
364                 // remove from inserted objects buffer
365                 insertedObjectsBuffer.removeObjectAtIndex( index );
366             }
367 
368             // now forget the object ever existed.
369             forgetObject( anObject );
370 
371             // we're done
372             return;
373 		}
374 		else // otherwise add to deleted objects list
375 		{
376 			deletedObjects.addObject( anObject );
377 		}
378 		
379 		// remove from updated objects if necessary
380 		i = updatedObjects.indexOfIdenticalObject( anObject );
381 		if ( i != NSArray.NotFound )
382         {
383             updatedObjects.removeObjectAtIndex( i );
384         }
385 
386         // add to buffer
387         deletedObjectsBuffer.addObject( anObject );            
388         deletedIDsBuffer.addObject( globalIDForObject( anObject ) );            
389     }
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         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         parentStore.editingContextDidForgetObjectWithGlobalID(
410             aContext, aGlobalID );
411     }
412 
413     /***
414     * Returns a read-only List of registered editors of this
415     * editing context.
416     */
417     public NSArray editors ()
418     {
419         NSMutableArray result = new NSMutableArray();
420         Object o;
421         Iterator i = editorSet.iterator();
422         while ( i.hasNext() )
423         {
424             o = ((WeakReference)i.next()).get();
425             if ( o != null )
426             {
427                 result.addObject( o );
428             }
429             else
430             {
431                 i.remove();
432             }
433         }
434         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         Object result = registrar.objectForGlobalID( aGlobalID );
460         
461         // if not registered in our context
462         if ( result == null )
463         {
464             // get the object registered into our context
465             result = parentStore.faultForGlobalID( aGlobalID, this );
466         }
467         
468         // if our context is not the specified context
469         if ( aContext != this ) 
470         {
471             result = registerClone( aGlobalID, this, result, aContext );
472         }
473         
474         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         return parentStore.faultForRawRow(
489             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         return parentStore.faultForRawRow(
506             aDictionary, anEntityName, aContext );
507     }
508 
509     /***
510     * Returns the fetch timestamp for this editing context.
511     */
512     public double fetchTimestamp ()
513     {
514         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         EOGlobalID id = registrar.globalIDForObject( anObject );
526         if ( id == null ) 
527         {
528             System.err.println( 
529                 "EOEditingContext.forgetObject: not registered: " + anObject );
530             return;
531         }
532         
533         // unregister object
534         registrar.forgetObject( anObject );   
535         
536         // remove from all, inserted, updated, and deleted lists
537         int index;
538         index = updatedObjects.indexOfIdenticalObject( anObject );
539         if ( index != NSArray.NotFound )
540         {
541             updatedObjects.removeObjectAtIndex( index );   
542         }
543         index = insertedObjects.indexOfIdenticalObject( anObject );
544         if ( index != NSArray.NotFound )
545         {
546             insertedObjects.removeObjectAtIndex( index );   
547             return;
548         }
549         index = deletedObjects.indexOfIdenticalObject( anObject );
550         if ( index != NSArray.NotFound )
551         {
552             deletedObjects.removeObjectAtIndex( index );   
553             return;
554         }
555 
556         // notify parent context
557         parentStore.editingContextDidForgetObjectWithGlobalID( this, id );
558     }
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         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         NSMutableArray result = new NSMutableArray();
577         Iterator it = anObjectList.iterator();
578         while ( it.hasNext() )
579         {
580             result.add( globalIDForObject( it.next() ) );
581         }
582         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         if ( updatedObjects.count() > 0 ) return true;
592         if ( insertedObjects.count() > 0 ) return true;
593         if ( deletedObjects.count() > 0 ) return true;
594         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         Object existingObject = registrar.objectForGlobalID( aGlobalID );
614         
615         // if not registered in our context
616         if ( existingObject == null )
617         {
618             // get the object registered into our context
619             existingObject = parentStore.faultForGlobalID( aGlobalID, this );
620         }
621         
622         if ( aContext == this )
623         {
624             // initialize the object
625             parentStore.initializeObject(
626                 /*(EOEnterpriseObject)*/existingObject, aGlobalID, this );
627         }
628         else // ( aContext != this )
629         {
630             // translates child relationships
631             copy( this, existingObject, aContext, anObject );
632         }
633 
634         aContext.registrar.setCommitSnapshot( anObject, null );
635         aContext.registrar.setCurrentSnapshot( anObject, null );
636     }
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         insertObjectWithGlobalID( 
646             anObject, new EOTemporaryGlobalID() );
647     }
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         willChange();
659         
660         // if this object was marked for deletion
661         int index = deletedObjects.indexOfIdenticalObject( anObject );
662         if ( index != NSArray.NotFound )
663         {
664              // don't need to re-register: just update the lists
665             
666              // remove from deleted list
667              deletedObjects.removeObjectAtIndex( index );
668     
669              // if in the deleted ids buffer         
670              index = deletedIDsBuffer.indexOfIdenticalObject( anObject );
671              if ( index != NSArray.NotFound )
672              {
673                  // remove from deleted ids buffer
674                  deletedIDsBuffer.removeObjectAtIndex( index );
675              }
676     
677              // if in the deleted objects buffer         
678              index = deletedObjectsBuffer.indexOfIdenticalObject( anObject );
679              if ( index != NSArray.NotFound )
680              {
681                  // remove from deleted objects buffer
682                  deletedObjectsBuffer.removeObjectAtIndex( index );
683              }
684              else // not in the deleted objects buffer
685              {
686                  // add to the inserted objects buffer
687                  insertedObjectsBuffer.addObject( anObject );
688              }
689              
690              // we're done
691              return;
692         }
693 
694         // make sure object is not already in editing context
695         if ( objectForGlobalID( aGlobalID ) != null )
696         {
697             throw new WotonomyException( 
698                 "Tried to insert but object was already registered:" 
699                 + aGlobalID );
700         }
701         
702         // register object
703         recordObject( anObject, aGlobalID );
704         
705         // add to inserted list
706         insertedObjects.addObject( anObject );
707         // add to buffer
708         insertedObjectsBuffer.addObject( anObject );
709     }
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         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         willChange();
730         
731         invalidateAllObjectsQuietly();
732         
733         // post notification
734         NSNotificationCenter.defaultCenter().postNotification(
735             new NSNotification(
736                 InvalidatedAllObjectsInStoreNotification, this ) );
737     }
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         NSMutableArray ids = new NSMutableArray( registrar.registeredGlobalIDs() );
748 
749         // track of discarded IDs (from inserted objects)
750         NSMutableArray discardedIDs = new NSMutableArray();
751 
752         // refault all objects
753         EOGlobalID id;
754         Object o;
755         Enumeration e = ids.objectEnumerator();
756         while ( e.hasMoreElements() )
757         {
758             id = (EOGlobalID) e.nextElement();
759             o = objectForGlobalID( id );
760 
761             // some objects may have been manually discarded
762             if ( o != null )
763             {
764                 // don't refault newly inserted objects
765                 if ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound )
766                 {
767                     refaultObject( o, id, this );
768                 }
769                 else
770                 {
771                     // discard inserted objects
772                     forgetObject( o );
773                     discardedIDs.add( id );
774                 }
775                 invalidatedObjectsBuffer.add( o );
776             }
777             invalidatedIDsBuffer.add( id );
778         }
779         ids.removeAll( discardedIDs );
780         
781         // call to parent store (should call this after posting instead?)
782         isInvalidating = true;        
783         parentStore.invalidateObjectsWithGlobalIDs( ids );
784         isInvalidating = false;        
785     }
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         willChange();
797 
798         // call to parent to invalidate objects
799         parentStore.invalidateObjectsWithGlobalIDs( anArray );
800 
801         Object o;
802         EOGlobalID id;
803         Iterator it = anArray.iterator();
804         while ( it.hasNext() )
805         {
806             id = (EOGlobalID) it.next();
807             if ( id != null )
808             {
809                 o = objectForGlobalID( id );
810                 if ( o != null )
811                 {
812                     Object result = notifyDelegate(
813                         "editingContextShouldInvalidateObject",
814                         new Class[] { EOEditingContext.class, Object.class, EOGlobalID.class },
815                         new Object[] { this, o, id } );
816                     if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
817                     {
818                         // refault the object
819                         refaultObject( o, id, this );
820                         invalidatedObjectsBuffer.add( o );
821                         invalidatedIDsBuffer.add( id );
822                     }
823                 }
824             }
825             else
826             {
827                 throw new WotonomyException(
828                     "Attempted to invalidate a null global id: " + anArray );
829             }
830         }
831         
832     }
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         return parentStore.isObjectLockedWithGlobalID(
855             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         parentStore.lockObjectWithGlobalID( 
872             globalIDForObject( anObject ), this );
873     }
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         parentStore.lockObjectWithGlobalID(
886             aGlobalID, aContext );
887     }
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         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         if ( messageHandler == null ) return null;
905         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         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         NSArray result = null;
937         
938 if ( aContext == this )
939 {
940     throw new WotonomyException( "Assert failed: calling objectsForSourceGlobalID on ourself." );   
941 }
942         Object source = registrar.objectForGlobalID( aGlobalID );
943         
944         // if not registered in our context
945         if ( source == null )
946         {
947             // get the object registered into our context
948             result = parentStore.objectsForSourceGlobalID( 
949                 aGlobalID, aRelationshipKey, this );
950         }
951         else // source is registered in our context
952         {
953             // get existing value
954             Object value;
955             if ( source instanceof EOKeyValueCoding )
956             {
957                 value = ((EOKeyValueCoding)source).storedValueForKey( 
958                     aRelationshipKey );
959             }
960             else // handle directly
961             {
962                 value = EOKeyValueCodingSupport.storedValueForKey( 
963                     source, aRelationshipKey );
964             }
965             
966             // if we don't have a valid value on our object
967             if ( ( value == null ) 
968             || ( ( value instanceof ArrayFault ) 
969               && ( !((ArrayFault)value).isFetched() ) ) )
970             {
971                 // do the same as if the source was null
972                 result = parentStore.objectsForSourceGlobalID( 
973                     aGlobalID, aRelationshipKey, this );
974                     
975                 // set our value since we have it
976                 if ( source instanceof EOKeyValueCoding )
977                 {
978                     ((EOKeyValueCoding)source).takeStoredValueForKey( 
979                         result, aRelationshipKey );
980                 }
981                 else // handle directly
982                 {
983                     EOKeyValueCodingSupport.takeStoredValueForKey( 
984                         source, result, aRelationshipKey );
985                 }
986             }
987             else
988             if ( ( value instanceof ArrayFault ) 
989               && ( !((ArrayFault)value).isFetched() ) )
990             {
991                 // do the same as if the source was null
992                 result = parentStore.objectsForSourceGlobalID( 
993                     aGlobalID, aRelationshipKey, this );
994             }
995             else
996             if ( value instanceof NSArray )
997             {
998                 result = (NSArray) value;
999             }
1000             else // not NSArray
1001             if ( value instanceof Collection )
1002             {
1003                 // convert to NSArray
1004                 result = new NSArray( (Collection) value );
1005             }
1006             else
1007             {
1008                 throw new WotonomyException( 
1009                     "Relationship key did not return a collection: " 
1010                     + aGlobalID + " : " + aRelationshipKey );   
1011             }
1012         }
1013             
1014         // if our context is not the specified context
1015         if ( aContext != this ) 
1016         {
1017             result = (NSArray) clone( this, result, aContext );
1018         }
1019         
1020         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         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         if ( aContext == this )
1049         {
1050             Object result = notifyDelegate(
1051                 "editingContextShouldFetchObjects",
1052                 new Class[] { EOEditingContext.class, EOFetchSpecification.class },
1053                 new Object[] { aContext, aFetchSpec } );
1054             if ( result instanceof NSArray ) return (NSArray) result;
1055         }
1056         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         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         boolean postStoreInfo = 
1094             ( insertedObjectsBuffer.size() + 
1095               updatedObjectsBuffer.size() +
1096               deletedIDsBuffer.size() +
1097               invalidatedIDsBuffer.size() > 0 );
1098         
1099         NSMutableDictionary storeInfo = new NSMutableDictionary();
1100         if ( postStoreInfo )
1101         {
1102             storeInfo.setObjectForKey( 
1103                 globalIDsForObjects( insertedObjectsBuffer ), 
1104     //            globalIDsForObjects( insertedObjects ), 
1105                 EOObjectStore.InsertedKey );
1106             storeInfo.setObjectForKey( 
1107                 globalIDsForObjects( updatedObjectsBuffer ), 
1108     //            globalIDsForObjects( updatedObjects ), 
1109                 EOObjectStore.UpdatedKey );
1110             storeInfo.setObjectForKey( 
1111                 new NSArray( (Collection) deletedIDsBuffer ), 
1112     //            globalIDsForObjects( deletedObjects ), 
1113                 EOObjectStore.DeletedKey );
1114             storeInfo.setObjectForKey( 
1115                 new NSArray( (Collection) invalidatedIDsBuffer ), 
1116                 EOObjectStore.InvalidatedKey );
1117         }
1118 
1119         // broadcast ObjectsChangedInEditingContextNotification
1120         // for the benefit of attached display groups
1121         
1122         boolean postContextInfo = 
1123             ( insertedObjectsBuffer.size() + 
1124               updatedObjectsBuffer.size() +
1125               deletedObjectsBuffer.size() +
1126               invalidatedObjectsBuffer.size() > 0 );
1127         
1128         NSMutableDictionary contextInfo = new NSMutableDictionary();
1129 
1130         if ( postContextInfo )
1131         {
1132                 
1133             contextInfo.setObjectForKey( 
1134                new NSArray( (Collection) insertedObjectsBuffer ), 
1135     //            new NSArray( (Collection) insertedObjects ), 
1136                 EOObjectStore.InsertedKey );
1137             contextInfo.setObjectForKey( 
1138                 new NSArray( (Collection) updatedObjectsBuffer ),  
1139     //            new NSArray( (Collection) updatedObjects ),  
1140                 EOObjectStore.UpdatedKey );
1141             contextInfo.setObjectForKey( 
1142                 new NSArray( (Collection) deletedObjectsBuffer ),  
1143     //            new NSArray( (Collection) deletedObjects ),  
1144                 EOObjectStore.DeletedKey );
1145             contextInfo.setObjectForKey( 
1146                 new NSArray( (Collection) invalidatedObjectsBuffer ),  
1147                 EOObjectStore.InvalidatedKey );
1148         }
1149     
1150         // update the current snapshots
1151         
1152         Object o;
1153         Iterator it;
1154         it = insertedObjectsBuffer.iterator();
1155         while ( it.hasNext() )
1156         {
1157             o = it.next();
1158             registrar.setCurrentSnapshot( o, takeSnapshot( o ) );
1159         }
1160         it = updatedObjectsBuffer.iterator();
1161         while ( it.hasNext() )
1162         {
1163             o = it.next();
1164             registrar.setCurrentSnapshot( o, takeSnapshot( o ) );
1165         }
1166 
1167         // clear buffers
1168         
1169         insertedObjectsBuffer.removeAllObjects();
1170         updatedObjectsBuffer.removeAllObjects();
1171         deletedObjectsBuffer.removeAllObjects();
1172         deletedIDsBuffer.removeAllObjects();
1173         invalidatedObjectsBuffer.removeAllObjects();
1174         invalidatedIDsBuffer.removeAllObjects();
1175 
1176         // post notifications (does order matter?)
1177         
1178         if ( postStoreInfo )
1179         {
1180             NSNotificationCenter.defaultCenter().postNotification(
1181                 new NSNotification(
1182                     ObjectsChangedInStoreNotification, this, storeInfo ) );
1183         }
1184         
1185         if ( postContextInfo )
1186         {
1187             NSNotificationCenter.defaultCenter().postNotification( 
1188                 new NSNotification(
1189                     ObjectsChangedInEditingContextNotification, this, contextInfo ) );
1190         }
1191 
1192     }
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         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         boolean inserted = false;
1218         boolean updated = false;
1219         boolean deleted = false;
1220 
1221         // is the object already registered?
1222         EOGlobalID existingID = globalIDForObject( anObject );
1223         if ( existingID != null )
1224         {
1225             // remember object state
1226             int index;
1227             index = insertedObjects.indexOfIdenticalObject( anObject );
1228             if ( index != NSArray.NotFound ) inserted = true;    
1229             index = updatedObjects.indexOfIdenticalObject( anObject );
1230             if ( index != NSArray.NotFound ) updated = true;    
1231             index = deletedObjects.indexOfIdenticalObject( anObject );
1232             if ( index != NSArray.NotFound ) deleted = true;    
1233             // forget the object
1234             forgetObject( anObject );
1235         }
1236 
1237         // is the global id already in use?
1238         Object existingObject = objectForGlobalID( aGlobalID );
1239         if ( existingObject != null )
1240         {
1241             // forget it (don't worry about state?)
1242             forgetObject( existingObject );
1243         }
1244 
1245         registrar.registerObject( anObject, aGlobalID );
1246         
1247         // restore state if necessary
1248         if ( inserted ) insertedObjects.addObject( anObject );
1249         if ( updated ) updatedObjects.addObject( anObject );
1250         if ( deleted ) deletedObjects.addObject( anObject );
1251     }
1252 
1253     /***
1254     * Undoes the last undo operation.
1255     */
1256     public void redo ()
1257     {
1258         //TODO: not supported yet
1259         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         fireWillSaveChanges();
1271         refaultObjects();
1272     }
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         aContext.registrar.setCurrentSnapshot( anObject, null );
1284 
1285         ignoreChanges = true;
1286         parentStore.refaultObject( anObject, aGlobalID, aContext );
1287         ignoreChanges = false;
1288         
1289 		// remove from updated objects if necessary
1290 		int i = updatedObjects.indexOfIdenticalObject( anObject );
1291 		if ( i != NSArray.NotFound )
1292         {
1293             updatedObjects.removeObjectAtIndex( i );
1294         }
1295 
1296         // add to invalidated notification queue
1297         invalidatedObjectsBuffer.addObject( anObject );
1298         invalidatedIDsBuffer.addObject( aGlobalID );
1299     }
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         Iterator it = registeredObjects().iterator();
1314         while ( it.hasNext() )
1315         {
1316             o = it.next();
1317             if ( ( updatedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound )
1318             &&   ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound )
1319             &&   ( deletedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) )
1320             {
1321                 id = globalIDForObject( o );
1322                 refaultObject( o, id, this );
1323             }
1324         }
1325     }
1326 
1327     /*** 
1328     * Calls editingContextWillSaveChanges() on all editors,
1329     * and then calls invalidateAllObjects().
1330     */ 
1331     public void refetch ()
1332     {
1333         fireWillSaveChanges();
1334         invalidateAllObjects();
1335     }
1336 
1337     /***
1338     * Returns a read-only List of all objects registered in this
1339     * editing context.
1340     */
1341     public NSArray registeredObjects ()
1342     {
1343         return registrar.registeredObjects();
1344     }
1345 
1346     /***
1347     * Unregisters the specified editor with this editing context.
1348     */
1349     public void removeEditor ( Object anObject )
1350     {
1351         if ( anObject == null ) return;
1352         
1353         Object o;
1354         Iterator i = editorSet.iterator();
1355         while ( i.hasNext() )
1356         {
1357             o = ((WeakReference)i.next()).get();
1358             if ( ( o == null ) || ( o == anObject ) )
1359             {
1360                 i.remove();
1361             }
1362         }
1363     }
1364 
1365     /***
1366     * Unregisters all objects from this editing context,
1367     * and resets the fetch timestamp.
1368     */
1369     public void reset ()
1370     {
1371         Iterator it = registeredObjects().iterator();
1372         while ( it.hasNext() )
1373         {
1374             forgetObject( it.next() );   
1375         }
1376         fetchTimestamp = 0; //FIXME: reset timestamp properly
1377     }
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         willChange();
1390         fireWillSaveChanges();
1391 
1392         Iterator it;
1393         
1394         // forget inserted objects
1395         it = new NSArray( insertedObjects ).iterator();
1396         while ( it.hasNext() )
1397         {
1398             forgetObject( it.next() );
1399         }
1400 
1401         EOGlobalID id;
1402         Object o;
1403         byte[] snapshot;
1404 
1405         // re-initialize updated objects
1406         it = new NSArray( updatedObjects ).iterator();
1407         while ( it.hasNext() )
1408         {
1409             o = it.next();
1410             snapshot = (byte[]) registrar.getCommitSnapshot( o );
1411             if ( snapshot != null )
1412             {
1413                 applySnapshot( snapshot, o );
1414             }
1415             registrar.setCommitSnapshot( o, null );
1416             updatedObjectsBuffer.addObject( o );
1417         }
1418 
1419         // re-initialize deleted objects
1420         it = new NSArray( deletedObjects ).iterator();
1421         while ( it.hasNext() )
1422         {
1423             o = it.next();
1424             snapshot = (byte[]) registrar.getCommitSnapshot( o );
1425             if ( snapshot != null )
1426             {
1427                 applySnapshot( snapshot, o );
1428             }
1429             registrar.setCommitSnapshot( o, null );
1430             updatedObjectsBuffer.addObject( o );
1431         }
1432 
1433         // reset lists
1434         insertedObjects.removeAllObjects(); // unneccessary?
1435         deletedObjects.removeAllObjects();
1436         updatedObjects.removeAllObjects();
1437 
1438         // post notification
1439         processRecentChanges();        
1440     }
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         EOObjectStore parent = parentObjectStore();
1449         while ( parent instanceof EOEditingContext )
1450         {
1451             parent = ((EOEditingContext)parent).parentObjectStore();   
1452         }
1453         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         willChange();
1467 
1468         // process any changes
1469         processRecentChanges();
1470         
1471         // set up user info for notification to be posted.
1472         NSMutableDictionary userInfo = new NSMutableDictionary();
1473         userInfo.setObjectForKey( 
1474             new NSArray( (Collection) insertedObjects ), 
1475             EOObjectStore.InsertedKey );
1476         userInfo.setObjectForKey( 
1477             new NSArray( (Collection) updatedObjects ),  
1478             EOObjectStore.UpdatedKey );
1479         userInfo.setObjectForKey( 
1480             new NSArray( (Collection) deletedObjects ),  
1481             EOObjectStore.DeletedKey );
1482         
1483         // notify the editors
1484         fireWillSaveChanges();
1485 
1486         // notify the delegate
1487         notifyDelegate(
1488             "editingContextWillSaveChanges",
1489             new Class[] { EOEditingContext.class },
1490             new Object[] { this } );
1491 
1492         // needed for notification handling
1493         isInvalidating = true;
1494         try
1495         {
1496             // ask parent to save us
1497             parentStore.saveChangesInEditingContext( this );
1498         }
1499         catch ( RuntimeException e )
1500         {
1501             // unset save flag and rethrow
1502             isInvalidating = false;
1503             throw e;
1504         }
1505         isInvalidating = false;
1506         
1507         // no exceptions: proceed!
1508         
1509         Object o, key;
1510         Iterator it;
1511         
1512         // update the committed snapshots
1513         it = insertedObjects.iterator();
1514         while ( it.hasNext() )
1515         {
1516             o = it.next();
1517             registrar.setCommitSnapshot( o, null );
1518             registrar.setCurrentSnapshot( o, null );
1519         }
1520         it = updatedObjects.iterator();
1521         while ( it.hasNext() )
1522         {
1523             o = it.next();
1524             registrar.setCommitSnapshot( o, null );
1525             registrar.setCurrentSnapshot( o, null );
1526         }
1527         
1528         // clear the lists
1529         updatedObjects.removeAllObjects();
1530         insertedObjects.removeAllObjects();
1531         it = new NSArray( deletedObjects() ).iterator();
1532         while ( it.hasNext() )
1533         {   // parent is doing this as well?
1534             forgetObject( it.next() );   
1535         }
1536         
1537         // post notification
1538         NSNotificationCenter.defaultCenter().postNotification(
1539             new NSNotification(
1540                 EditingContextDidSaveChangesNotification, this, userInfo ) );
1541     }
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         List deletes = new NSArray( aContext.deletedObjects() );
1556         it = deletes.iterator();
1557         while ( it.hasNext() )
1558         {
1559             o = it.next();
1560             EOGlobalID id = aContext.globalIDForObject( o );
1561             Object localVersion = objectForGlobalID( id );
1562             if ( localVersion == null )
1563             {
1564                 // make a local copy and register it
1565                 localVersion = registerClone( id, aContext, o, this );
1566                 if ( localVersion == null )
1567                 {
1568                     throw new WotonomyException(
1569                         "Deleted object could not be serialized: " 
1570                         + id + " : " + o );
1571                 }
1572             }
1573             else // we have a local version, copy changes
1574             {
1575                 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             deleteObject( localVersion );
1580         }
1581 
1582         // process inserts - all inserts are new objects
1583         List inserts = new NSArray( aContext.insertedObjects() );
1584         it = inserts.iterator();
1585         while ( it.hasNext() )
1586         {
1587             o = it.next();
1588             // make a local copy and register it
1589             EOGlobalID id = aContext.globalIDForObject( o );
1590             willChange(); // need to mark editing context as changed
1591             Object localVersion = registerClone( id, aContext, o, this );
1592             if ( localVersion == null )
1593             {
1594                 throw new WotonomyException(
1595                     "Inserted object could not be