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 serialized: " 
1596                     + o );
1597             }
1598             // insert our copy manually so a new id is not generated
1599             insertedObjects.addObject( localVersion );
1600             insertedObjectsBuffer.addObject( localVersion );
1601         }
1602 
1603         // process updates
1604         List updates = new NSArray( aContext.updatedObjects() ); 
1605         it = updates.iterator();
1606         while ( it.hasNext() )
1607         {
1608             willChange(); // need to mark editing context as changed
1609             o = it.next();
1610             EOGlobalID id = aContext.globalIDForObject( o );
1611             Object localVersion = objectForGlobalID( id );
1612             if ( localVersion == null )
1613             {
1614                 // make a local copy and register it
1615                 localVersion = registerClone( id, aContext, o, this );
1616                 if ( localVersion == null )
1617                 {
1618                     throw new WotonomyException(
1619                         "Updated object could not be serialized: " 
1620                         + id + " : " + o );
1621                 }
1622                 if ( id.isTemporary() )
1623                 {
1624                     // mark this object as inserted
1625                     insertedObjects.addObject( localVersion );
1626                     insertedObjectsBuffer.addObject( localVersion );
1627                 }
1628                 else
1629                 {
1630                     // mark this object as updated
1631                     updatedObjects.addObject( localVersion );
1632                     
1633                     // notify of update only if not on deleted list
1634                     if ( deletedObjectsBuffer.indexOfIdenticalObject( 
1635                         localVersion ) == NSArray.NotFound )
1636                     {
1637                         updatedObjectsBuffer.addObject( localVersion );
1638                     }
1639                 }
1640             }
1641             else // we have a local version, copy changes
1642             {
1643                 copy( aContext, o, this, localVersion );
1644                 // copy marks the object as updated 
1645             }
1646         }
1647         
1648     }
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         if ( anObject == null ) delegate = null;
1659         delegate = new WeakReference( anObject );
1660     }
1661 
1662     /***
1663     * Sets the fetch timestamp for this editing context.
1664     */
1665     public void setFetchTimestamp ( 
1666         double aDouble )
1667     {
1668         fetchTimestamp = aDouble;
1669     }
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         lockBeforeModify = locksObjects;
1692     }
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         if ( anObject == null ) messageHandler = null;
1703         messageHandler = new WeakReference( anObject );
1704     }
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         propagateDeletesAfterEvent = propagatesDeletes;
1716     }
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         stopValidationAfterError = stopsValidation;
1734     }
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         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         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         return updatedObjectsProxy;
1791     }
1792 
1793     /***
1794     * Notify editors of changes.
1795     */    
1796     private void fireWillSaveChanges()
1797     {
1798         Object o = null;
1799         Iterator i = editors().iterator();
1800         while ( i.hasNext() )
1801         {
1802             try
1803             {
1804                 o = i.next();
1805                 NSSelector.invoke( "editingContextWillSaveChanges", 
1806                     new Class[] { EOEditingContext.class }, o, this );
1807             }
1808             catch ( NoSuchMethodException e )
1809             {
1810                 // ignore: not implemented
1811             }
1812             catch ( Exception exc )
1813             {
1814                 // log to standard error
1815                 System.err.println( "Error while notifying editor of pending save: " + o );
1816                 exc.printStackTrace();
1817             }
1818         }
1819     }
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         willChange();
1836         if ( InvalidatedAllObjectsInStoreNotification
1837             .equals( aNotification.name() ) )
1838         {
1839             refaultObjects();
1840             
1841             // relay notification
1842             NSNotificationCenter.defaultCenter().postNotification(
1843                 new NSNotification(
1844                     InvalidatedAllObjectsInStoreNotification, this ) );
1845         }
1846         else
1847         if ( EOGlobalID.GlobalIDChangedNotification
1848             .equals( aNotification.name() ) )
1849         {
1850             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             Enumeration e = userInfo.keyEnumerator();
1858             while ( e.hasMoreElements() )
1859             {
1860                 id = (EOGlobalID) e.nextElement();
1861                 o = objectForGlobalID( id );
1862                 if ( o != null )
1863                 {
1864                     // record object is assumed to handle key updates
1865                     recordObject( o, (EOGlobalID) userInfo.objectForKey( id ) );
1866                 }
1867             }
1868         }
1869         else
1870         if ( EOObjectStore.ObjectsChangedInStoreNotification
1871             .equals( aNotification.name() ) )
1872         {
1873             //System.out.println( "EOEditingContext.handleNotification: " + aNotification + " : " + this );            
1874             // post so child contexts are notified
1875             willChange();
1876 
1877             Object o;
1878             EOGlobalID id;
1879             Enumeration e;
1880             NSDictionary userInfo = aNotification.userInfo();
1881             
1882             // inserted objects are ignored
1883             
1884             // existing deleted objects are removed
1885             NSArray deletes = (NSArray) userInfo.objectForKey( 
1886                 EOObjectStore.DeletedKey );
1887             e = deletes.objectEnumerator();
1888             while ( e.hasMoreElements() )
1889             {
1890                 id = (EOGlobalID) e.nextElement();
1891                 o = objectForGlobalID( id );
1892                 if ( o != null )
1893                 {
1894                     //System.out.println( "EOEditingContext: deleted: " + id );
1895                     forgetObject( o );
1896                     deletedObjectsBuffer.addObject( o );            
1897                     deletedIDsBuffer.addObject( id );            
1898                 }
1899             }
1900             
1901             // existing updated objects are merged
1902             NSArray updates = (NSArray) userInfo.objectForKey( 
1903                 EOObjectStore.UpdatedKey );
1904             e = updates.objectEnumerator();
1905             while ( e.hasMoreElements() )
1906             {
1907                 id = (EOGlobalID) e.nextElement();
1908                 o = objectForGlobalID( id );
1909                 if ( o != null )
1910                 {
1911                     //System.out.println( "EOEditingContext: updated: " + id );
1912                     if ( updatedObjects // only update if unchanged
1913                         .indexOfIdenticalObject( o ) == NSArray.NotFound )
1914                     {
1915                         refaultObject( o, id, this );
1916                         updatedObjectsBuffer.addObject( o );            
1917                     }
1918                     else
1919                     {
1920                         // notify user and/or merge
1921                         handleUpdateConflict( id, o );
1922                     }
1923                 }
1924             }
1925                 
1926             // existing invalidated objects are refaulted           
1927             NSArray invalidates = (NSArray) userInfo.objectForKey( 
1928                 EOObjectStore.InvalidatedKey );
1929             e = invalidates.objectEnumerator();
1930             while ( e.hasMoreElements() )
1931             {
1932                 id = (EOGlobalID) e.nextElement();
1933                 o = objectForGlobalID( id );
1934                 if ( o != null )
1935                 {
1936                     if ( updatedObjects // only invalidate if unchanged
1937                         .indexOfIdenticalObject( o ) == NSArray.NotFound )
1938                     {
1939                         refaultObject( o, id, this );
1940                     }
1941                     else
1942                     {
1943                         // notify user and/or merge
1944                         handleUpdateConflict( id, o );
1945                     }
1946                     if ( invalidatedObjectsBuffer
1947                         .indexOfIdenticalObject( o ) == NSArray.NotFound )
1948                     {
1949                         invalidatedObjectsBuffer.addObject( o ); 
1950                     }
1951                     if ( invalidatedIDsBuffer
1952                         .indexOfIdenticalObject( id ) == NSArray.NotFound )
1953                     {
1954                         invalidatedIDsBuffer.addObject( id );
1955                     }
1956                 }
1957             }
1958                
1959         }
1960     }
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         if ( isInvalidating ) 
1975         {
1976             ignoreChanges = true;
1977             parentStore.refaultObject( anObject, anID, this );
1978             ignoreChanges = false;
1979             return;
1980         }
1981 
1982         Boolean result = (Boolean) notifyDelegate( 
1983             "editingContextShouldMergeChangesForObject", 
1984             new Class[] { EOEditingContext.class, Object.class },
1985             new Object[] { this, anObject } );
1986 
1987         if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )         
1988         {
1989             // do merge
1990             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         notifyDelegate( 
1999             "editingContextDidMergeChanges", 
2000             new Class[] { EOEditingContext.class },
2001             new Object[] { this } );
2002     }    
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             Map fetchSnapshot = committedSnapshotForObject( anObject );
2023             
2024             // get current snapshot
2025             Map currentSnapshot = currentEventSnapshotForObject( anObject );
2026             
2027             // diff against fetch snapshot
2028             Map currentDiff = new HashMap();
2029             i = currentSnapshot.keySet().iterator();
2030             while ( i.hasNext() )
2031             {
2032                 key = i.next();
2033                 if ( ! currentSnapshot.get( key ).equals( fetchSnapshot.get( key ) ) )
2034                 {
2035                     currentDiff.put( key, currentSnapshot.get( key ) );
2036                 }
2037             }
2038             
2039             // refault
2040             ignoreChanges = true;
2041             parentStore.refaultObject( anObject, anID, this );
2042             ignoreChanges = false;
2043             
2044             // get updated snapshot
2045             Map updatedSnapshot = convertSnapshotToDictionary( takeSnapshot( anObject ) );
2046             
2047             // diff against fetch snapshot
2048             Map updatedDiff = new HashMap();
2049             i = updatedSnapshot.keySet().iterator();
2050             while ( i.hasNext() )
2051             {
2052                 key = i.next();
2053                 if ( ! updatedSnapshot.get( key ).equals( fetchSnapshot.get( key ) ) )
2054                 {
2055                     updatedDiff.put( key, updatedSnapshot.get( key ) );
2056                 }
2057             }
2058             
2059             // determine if there's a conflict
2060             boolean proceed = true;
2061             Set updatedKeys = updatedDiff.keySet();
2062             i = currentDiff.keySet().iterator();
2063            while ( i.hasNext() )
2064             {
2065                 if ( updatedKeys.contains( i.next() ) )
2066                 {
2067                     proceed = false;
2068                     break;
2069                 }
2070             }
2071             
2072             // if no conflicts, apply original diff to current object and exit
2073             if ( proceed )
2074             {
2075                 KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, currentDiff );
2076                 return true; // exit!
2077             }
2078         }
2079         catch ( Exception exc )
2080         {
2081             // log error to standard out
2082             exc.printStackTrace();
2083         }
2084         
2085         // notify user we're unable to merge
2086         notifyMessageHandler( MessageChangeConflict + anObject );
2087         return false;
2088     }
2089 
2090     /***
2091      * Sends the specified message to the message handler.
2092      */
2093     private void notifyMessageHandler( String aMessage )
2094     {
2095         Object handler = null;
2096         try
2097         {
2098             handler = messageHandler();
2099             if ( handler == null ) return;
2100             NSSelector.invoke( "editingContextPresentErrorMessage", 
2101                 new Class[] { EOEditingContext.class, String.class }, 
2102                 handler, this, aMessage );
2103         }
2104         catch ( NoSuchMethodException e )
2105         {
2106             // ignore: not implemented
2107         }
2108         catch ( Exception exc )
2109         {
2110             // log to standard error
2111             System.err.println( 
2112                 "Error while notifying message handler: " + 
2113                     handler + " : " + aMessage );
2114             exc.printStackTrace();
2115         }
2116     }
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             Object delegate = delegate();
2130             if ( delegate == null ) return null;
2131             return NSSelector.invoke( 
2132                 aMethodName, types, delegate, params );
2133         }
2134         catch ( NoSuchMethodException e )
2135         {
2136             // ignore: not implemented
2137         }
2138         catch ( Exception exc )
2139         {
2140             // log to standard error
2141             System.err.println( 
2142                 "Error while messaging delegate: " + 
2143                     delegate + " : " + aMethodName );
2144             exc.printStackTrace();
2145         }
2146         
2147         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         if ( ignoreChanges ) return;
2160 //NSNotificationCenter.defaultCenter().postNotification( "objectWillChange", this, new NSDictionary( "object", anObject ) );
2161 //new RuntimeException().printStackTrace();
2162 
2163         willChange();
2164 
2165         // mark as updated if not marked already
2166         int i = updatedObjects.indexOfIdenticalObject( anObject );
2167         if ( i == NSArray.NotFound )
2168         {
2169             // don't mark inserted objects as updated
2170             i = insertedObjects.indexOfIdenticalObject( anObject );
2171             if ( i == NSArray.NotFound )
2172             {
2173                 i = deletedObjects.indexOfIdenticalObject( anObject );
2174                 if ( i == NSArray.NotFound )
2175                 {
2176                     // add object
2177                     updatedObjects.addObject( anObject );
2178 
2179                     // record revert snapshot
2180                     registrar.setCommitSnapshot( anObject, takeSnapshot( anObject ) );
2181                 }
2182             }
2183         }
2184         
2185         // add to buffer
2186         if ( updatedObjectsBuffer.indexOfIdenticalObject( anObject )
2187                == NSArray.NotFound )
2188         {
2189             updatedObjectsBuffer.addObject( anObject );
2190         }
2191     }
2192 
2193     // static methods
2194     
2195     public static double defaultFetchTimestampLag ()
2196     {
2197         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         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         return retainsRegisteredObjects;
2227     }
2228 
2229     /***
2230     * Sets the global default fetch timestamp lag.
2231     */
2232     public static void setDefaultFetchTimestampLag ( 
2233         double aDouble )
2234     {
2235         defaultFetchTimestampLag = aDouble;
2236     }
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         defaultParentObjectStore = anObjectStore;
2246     }
2247 
2248     public static void setInstancesRetainRegisteredObjects ( 
2249         boolean retainsObjects )
2250     {
2251         retainsRegisteredObjects = retainsObjects;
2252     }
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         return "[EOEditingContext@"+Integer.toHexString(System.identityHashCode(this))+":"+
2281             " inserted="+idsForObjects(insertedObjects)+
2282             " updated="+idsForObjects(updatedObjects)+
2283             " deleted="+idsForObjects(deletedObjects)+
2284             " registered="+registrar.registeredGlobalIDs()+" ]";
2285     }
2286     private List idsForObjects( List objects )
2287     {
2288         List result = new LinkedList();
2289         Iterator i = objects.iterator();
2290         while ( i.hasNext() ) result.add( globalIDForObject( i.next() ) );
2291         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         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         NSDictionary values = convertSnapshotToDictionary( aSnapshot ); 
2316 
2317 //System.out.println( "applySnapshot: " + aSnapshot + " : " + values );
2318 //ignoreChanges = true;        
2319         willChange();
2320         KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, values );        
2321 //ignoreChanges = false;        
2322     }
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         Object clone = KeyValueCodingUtilities.thaw( aSnapshot, this, true );
2332         // get all keys for this object
2333         EOClassDescription classDesc =
2334             EOClassDescription.classDescriptionForClass( clone.getClass() );            
2335         List keys = new LinkedList();
2336         keys.addAll( classDesc.attributeKeys() ); 
2337         keys.addAll( classDesc.toOneRelationshipKeys() ); 
2338         keys.addAll( classDesc.toManyRelationshipKeys() ); 
2339 
2340         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         return KeyValueCodingUtilities.clone( 
2353             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         Object clone = KeyValueCodingUtilities.thaw( 
2377                 KeyValueCodingUtilities.freeze( 
2378                     aSource, aSourceContext, aSource, false ), 
2379                 aDestinationContext, false );
2380             
2381         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         aDestinationContext.ignoreChanges = true;
2386         copy( aSourceContext, aSource, aDestinationContext, clone );
2387         aDestinationContext.ignoreChanges = false;
2388 
2389         // return our clone
2390         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         EOObserverCenter.notifyObserversObjectWillChange( aDestination );
2405         KeyValueCodingUtilities.copy( aSourceContext, aSource, aDestinationContext, aDestination );
2406         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         EOObserverCenter.notifyObserversObjectWillChange( this );        
2418         runLater();
2419     }
2420     
2421 	/***
2422 	* Called to ensure that processRecentChanges
2423 	* will be called on the next event loop.
2424 	*/
2425 	private void runLater()
2426 	{
2427 		if ( ! willRunLater )
2428 		{
2429 			willRunLater = true;
2430             NSRunLoop.currentRunLoop().performSelectorWithOrder(
2431                 runLaterSelector, this, null, EditingContextFlushChangesRunLoopOrdering, null );
2432 		}
2433 	}
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     	processRecentChanges();
2444 		willRunLater = false;
2445 	}
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         public Registrar( EOEditingContext aContext )
2463         {
2464             context = aContext;
2465             IDsToObjects = new NSMutableDictionary();
2466             objectsToIDs = new NSMutableDictionary();
2467             objectsToCommitSnapshots = new NSMutableDictionary();
2468             objectsToCurrentSnapshots = new NSMutableDictionary();
2469             comparisonKey = new ReferenceKey();
2470         }
2471         
2472         public NSArray registeredObjects()
2473         {
2474             return IDsToObjects.allValues();
2475         }
2476         
2477         public NSArray registeredGlobalIDs()
2478         {
2479             return IDsToObjects.allKeys();
2480         }
2481         
2482         public Object objectForGlobalID( EOGlobalID aGlobalID )
2483         {
2484             return IDsToObjects.objectForKey( aGlobalID );
2485         }
2486         
2487         public EOGlobalID globalIDForObject( Object anObject )
2488         {
2489             comparisonKey.set( anObject );
2490             return (EOGlobalID) objectsToIDs.objectForKey( comparisonKey );
2491         }
2492         
2493         public byte[] getCommitSnapshot( Object anObject )
2494         {
2495             comparisonKey.set( anObject );
2496             return (byte[]) objectsToCommitSnapshots.objectForKey( comparisonKey );
2497         }
2498         
2499         public void setCommitSnapshot( Object anObject, byte[] aSnapshot )
2500         {
2501             if ( aSnapshot == null )
2502             {
2503                 comparisonKey.set( anObject );
2504                 objectsToCommitSnapshots.removeObjectForKey( comparisonKey );
2505             }
2506             else
2507             {
2508                 objectsToCommitSnapshots.setObjectForKey( 
2509                     aSnapshot, new ReferenceKey( anObject ) );
2510             }
2511         }
2512         
2513         public byte[] getCurrentSnapshot( Object anObject )
2514         {
2515             comparisonKey.set( anObject );
2516             return (byte[]) objectsToCurrentSnapshots.objectForKey( comparisonKey );
2517         }
2518         
2519         public void setCurrentSnapshot( Object anObject, byte[] aSnapshot )
2520         {
2521             if ( aSnapshot == null )
2522             {
2523                 comparisonKey.set( anObject );
2524                 objectsToCurrentSnapshots.removeObjectForKey( comparisonKey );
2525             }
2526             else
2527             {
2528                 objectsToCurrentSnapshots.setObjectForKey( 
2529                     aSnapshot, new ReferenceKey( anObject ) );
2530             }
2531         }
2532         
2533         public void registerObject( Object anObject, EOGlobalID aGlobalID )
2534         {
2535             IDsToObjects.setObjectForKey( anObject, aGlobalID );
2536             objectsToIDs.setObjectForKey( aGlobalID, new ReferenceKey( anObject ) );
2537             EOObserverCenter.addObserver( context, anObject );
2538         }
2539         
2540         public void forgetObject( Object anObject )
2541         {
2542             comparisonKey.set( anObject );
2543             Object id = objectsToIDs.objectForKey( comparisonKey );
2544             IDsToObjects.removeObjectForKey( id );
2545             objectsToIDs.removeObjectForKey( comparisonKey );
2546             EOObserverCenter.removeObserver( context, anObject );
2547         }
2548         
2549         public void disposeSnapshots( Object anObject )
2550         {
2551             setCommitSnapshot( anObject, null );
2552             setCurrentSnapshot( anObject, null );
2553         }
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             super( aContext );
2568             weakComparisonKey = new WeakReferenceKey();
2569         }
2570         
2571         public NSArray registeredObjects()
2572         {
2573             Object object;
2574             WeakReferenceKey weakKey;
2575             NSMutableArray result = new NSMutableArray();
2576             Enumeration e = new NSArray( objectsToIDs.allKeys() ).objectEnumerator();
2577             while ( e.hasMoreElements() )
2578             {
2579                 weakKey = (WeakReferenceKey) e.nextElement();
2580                 object = weakKey.get();
2581                 if ( object != null )
2582                 {
2583                     result.addObject( object );
2584                 }
2585                 else
2586                 {
2587                     // object has been released: perform cleanup
2588                     disposeObject( null, weakKey );
2589                 }
2590             }
2591             return result;
2592         }
2593         
2594         public Object objectForGlobalID( EOGlobalID aGlobalID )
2595         {
2596             WeakReference ref = (WeakReference) super.objectForGlobalID( aGlobalID );
2597             if ( ref == null ) return null;
2598             Object result = ref.get();
2599             if ( result == null )
2600             {
2601                 // clean up manually
2602                 IDsToObjects.removeObjectForKey( aGlobalID );
2603                 Iterator i = new LinkedList( objectsToIDs.allKeysForObject( ref ) ).iterator();
2604                 while ( i.hasNext() )
2605                 {
2606                     objectsToIDs.removeObjectForKey( i.next() );
2607                 }
2608                 disposeSnapshots( aGlobalID ); 
2609             }
2610             return result;
2611         }
2612         
2613         public byte[] getCommitSnapshot( Object anObject )
2614         {
2615             weakComparisonKey.set( anObject );
2616             return (byte[]) objectsToCommitSnapshots.objectForKey( weakComparisonKey );
2617         }
2618         
2619         public void setCommitSnapshot( Object anObject, byte[] aSnapshot )
2620         {
2621             if ( aSnapshot == null )
2622             {
2623                 weakComparisonKey.set( anObject );
2624                 objectsToCommitSnapshots.removeObjectForKey( weakComparisonKey );
2625             }
2626             else
2627             {
2628                 objectsToCommitSnapshots.setObjectForKey( 
2629                     aSnapshot, new WeakReferenceKey( anObject ) );
2630             }
2631         }
2632         
2633         public byte[] getCurrentSnapshot( Object anObject )
2634         {
2635             weakComparisonKey.set( anObject );
2636             return (byte[]) objectsToCurrentSnapshots.objectForKey( weakComparisonKey );
2637         }
2638         
2639         public void setCurrentSnapshot( Object anObject, byte[] aSnapshot )
2640         {
2641             if ( aSnapshot == null )
2642             {
2643                 weakComparisonKey.set( anObject );
2644                 objectsToCurrentSnapshots.removeObjectForKey( weakComparisonKey );
2645             }
2646             else
2647             {
2648                 objectsToCurrentSnapshots.setObjectForKey( 
2649                     aSnapshot, new WeakReferenceKey( anObject ) );
2650             }
2651         }
2652         
2653         public void registerObject( Object anObject, EOGlobalID aGlobalID )
2654         { // new net.wotonomy.ui.swing.ReferenceInspector( anObject );            
2655             IDsToObjects.setObjectForKey( new WeakReference( anObject ), aGlobalID );
2656             objectsToIDs.setObjectForKey( aGlobalID, new WeakReferenceKey( anObject ) );
2657             EOObserverCenter.addObserver( context, anObject );
2658         }
2659         
2660         public void forgetObject( Object anObject )
2661         {
2662             disposeObject( anObject, null );
2663         }
2664         
2665         // must specify one or the other
2666         private void disposeObject( Object anObject, WeakReferenceKey key )
2667         {
2668             if ( key == null ) key = new WeakReferenceKey( anObject );
2669             EOGlobalID id = (EOGlobalID) objectsToIDs.objectForKey( key );
2670             if ( id != null ) IDsToObjects.removeObjectForKey( id );
2671             objectsToIDs.removeObjectForKey( key );
2672             disposeSnapshots( id );
2673             if ( anObject != null )
2674             {
2675                 EOObserverCenter.removeObserver( context, anObject );
2676             }
2677         }
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         public ReferenceKey()
2690         {
2691             referent = null;
2692             hashCode = -1;
2693         }
2694         
2695         public ReferenceKey( Object anObject )
2696         {
2697             set( anObject );
2698         }
2699         
2700         public Object get()
2701         {
2702             return referent;
2703         }
2704         
2705         public void set( Object anObject )
2706         {
2707             referent = anObject;
2708             hashCode = anObject.hashCode();
2709         }
2710         
2711         /***
2712         * Returns the actual key's hash code.
2713         */
2714         public int hashCode()
2715         {
2716             return hashCode;   
2717         }
2718         
2719         /***
2720         * Compares by reference.
2721         */ 
2722         public boolean equals( Object anObject )
2723         {
2724             if ( anObject == this ) return true;
2725             if ( anObject instanceof ReferenceKey )
2726             {
2727                 return ((ReferenceKey)anObject).get() == referent;
2728             }
2729             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             super();
2742         }
2743         
2744         public WeakReferenceKey( Object anObject )
2745         {
2746             super( anObject );
2747         }
2748         
2749         public Object get()
2750         {
2751             return ((WeakReference)referent).get();
2752         }
2753         
2754         public void set( Object anObject )
2755         {
2756             referent = new WeakReference( anObject );
2757             hashCode = anObject.hashCode();
2758         }
2759         
2760         /***
2761         * Compares by reference.
2762         */ 
2763         public boolean equals( Object anObject )
2764         {
2765             if ( anObject == this ) return true;
2766             if ( anObject instanceof ReferenceKey )
2767             {
2768                 return ((ReferenceKey)anObject).get() == get();
2769             }
2770             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         public CompoundKey ( 
2789             Object anObject, String aString )
2790         {
2791             object = anObject;
2792             string = aString;
2793             hashCode = object.hashCode() + string.hashCode();
2794         }
2795         
2796         public int hashCode()
2797         {
2798             return hashCode;
2799         }
2800         
2801         public boolean equals( Object anObject )
2802         {
2803             if ( anObject instanceof CompoundKey )
2804             {
2805                 CompoundKey key = (CompoundKey) anObject;
2806                 return ( ( key.object == object ) && ( key.string.equals( string ) ) );
2807             }
2808             return false;  
2809         }
2810         
2811         public String toString()
2812         {
2813             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