1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
42
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
123
124
125 private boolean ignoreChanges;
126
127
128 private boolean willRunLater;
129
130
131
132 private boolean isInvalidating;
133
134
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
195
196
197
198
199 NSSelector handleNotification =
200 new NSSelector( "handleNotification",
201 new Class[] { NSNotification.class } );
202
203 NSNotificationCenter.defaultCenter().addObserver(
204 this, handleNotification, null, parentStore );
205
206 NSNotificationCenter.defaultCenter().addObserver(
207 this, handleNotification, EOGlobalID.GlobalIDChangedNotification, null );
208
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
243 if ( source == null )
244 {
245
246 result = parentStore.arrayFaultWithSourceGlobalID(
247 aGlobalID, aRelationshipKey, this );
248 }
249 else
250 {
251
252 Object value;
253 if ( source instanceof EOKeyValueCoding )
254 {
255 value = ((EOKeyValueCoding)source).storedValueForKey(
256 aRelationshipKey );
257 }
258 else
259 {
260 value = EOKeyValueCodingSupport.storedValueForKey(
261 source, aRelationshipKey );
262 }
263
264 if ( value == null )
265 {
266
267 result = parentStore.arrayFaultWithSourceGlobalID(
268 aGlobalID, aRelationshipKey, this );
269 }
270 else
271 if ( value instanceof NSArray )
272 {
273 result = (NSArray) value;
274 }
275 else
276 if ( value instanceof Collection )
277 {
278
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
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
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
355 i = insertedObjects.indexOfIdenticalObject( anObject );
356 if ( i != NSArray.NotFound )
357 {
358 insertedObjects.removeObjectAtIndex( i );
359
360
361 int index = insertedObjectsBuffer.indexOfIdenticalObject( anObject );
362 if ( index != NSArray.NotFound )
363 {
364
365 insertedObjectsBuffer.removeObjectAtIndex( index );
366 }
367
368
369 forgetObject( anObject );
370
371
372 return;
373 }
374 else
375 {
376 deletedObjects.addObject( anObject );
377 }
378
379
380 i = updatedObjects.indexOfIdenticalObject( anObject );
381 if ( i != NSArray.NotFound )
382 {
383 updatedObjects.removeObjectAtIndex( i );
384 }
385
386
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
439
440
441
442
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
456 EOGlobalID aGlobalID,
457 EOEditingContext aContext )
458 {
459 Object result = registrar.objectForGlobalID( aGlobalID );
460
461
462 if ( result == null )
463 {
464
465 result = parentStore.faultForGlobalID( aGlobalID, this );
466 }
467
468
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
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
534 registrar.forgetObject( anObject );
535
536
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
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
610 EOGlobalID aGlobalID,
611 EOEditingContext aContext )
612 {
613 Object existingObject = registrar.objectForGlobalID( aGlobalID );
614
615
616 if ( existingObject == null )
617 {
618
619 existingObject = parentStore.faultForGlobalID( aGlobalID, this );
620 }
621
622 if ( aContext == this )
623 {
624
625 parentStore.initializeObject(
626
627 }
628 else
629 {
630
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
661 int index = deletedObjects.indexOfIdenticalObject( anObject );
662 if ( index != NSArray.NotFound )
663 {
664
665
666
667 deletedObjects.removeObjectAtIndex( index );
668
669
670 index = deletedIDsBuffer.indexOfIdenticalObject( anObject );
671 if ( index != NSArray.NotFound )
672 {
673
674 deletedIDsBuffer.removeObjectAtIndex( index );
675 }
676
677
678 index = deletedObjectsBuffer.indexOfIdenticalObject( anObject );
679 if ( index != NSArray.NotFound )
680 {
681
682 deletedObjectsBuffer.removeObjectAtIndex( index );
683 }
684 else
685 {
686
687 insertedObjectsBuffer.addObject( anObject );
688 }
689
690
691 return;
692 }
693
694
695 if ( objectForGlobalID( aGlobalID ) != null )
696 {
697 throw new WotonomyException(
698 "Tried to insert but object was already registered:"
699 + aGlobalID );
700 }
701
702
703 recordObject( anObject, aGlobalID );
704
705
706 insertedObjects.addObject( anObject );
707
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
729 willChange();
730
731 invalidateAllObjectsQuietly();
732
733
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
747 NSMutableArray ids = new NSMutableArray( registrar.registeredGlobalIDs() );
748
749
750 NSMutableArray discardedIDs = new NSMutableArray();
751
752
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
762 if ( o != null )
763 {
764
765 if ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound )
766 {
767 refaultObject( o, id, this );
768 }
769 else
770 {
771
772 forgetObject( o );
773 discardedIDs.add( id );
774 }
775 invalidatedObjectsBuffer.add( o );
776 }
777 invalidatedIDsBuffer.add( id );
778 }
779 ids.removeAll( discardedIDs );
780
781
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
796 willChange();
797
798
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
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
835
836
837
838
839
840
841
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
860
861
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
934
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
945 if ( source == null )
946 {
947
948 result = parentStore.objectsForSourceGlobalID(
949 aGlobalID, aRelationshipKey, this );
950 }
951 else
952 {
953
954 Object value;
955 if ( source instanceof EOKeyValueCoding )
956 {
957 value = ((EOKeyValueCoding)source).storedValueForKey(
958 aRelationshipKey );
959 }
960 else
961 {
962 value = EOKeyValueCodingSupport.storedValueForKey(
963 source, aRelationshipKey );
964 }
965
966
967 if ( ( value == null )
968 || ( ( value instanceof ArrayFault )
969 && ( !((ArrayFault)value).isFetched() ) ) )
970 {
971
972 result = parentStore.objectsForSourceGlobalID(
973 aGlobalID, aRelationshipKey, this );
974
975
976 if ( source instanceof EOKeyValueCoding )
977 {
978 ((EOKeyValueCoding)source).takeStoredValueForKey(
979 result, aRelationshipKey );
980 }
981 else
982 {
983 EOKeyValueCodingSupport.takeStoredValueForKey(
984 source, result, aRelationshipKey );
985 }
986 }
987 else
988 if ( ( value instanceof ArrayFault )
989 && ( !((ArrayFault)value).isFetched() ) )
990 {
991
992 result = parentStore.objectsForSourceGlobalID(
993 aGlobalID, aRelationshipKey, this );
994 }
995 else
996 if ( value instanceof NSArray )
997 {
998 result = (NSArray) value;
999 }
1000 else
1001 if ( value instanceof Collection )
1002 {
1003
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
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 {
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
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
1105 EOObjectStore.InsertedKey );
1106 storeInfo.setObjectForKey(
1107 globalIDsForObjects( updatedObjectsBuffer ),
1108
1109 EOObjectStore.UpdatedKey );
1110 storeInfo.setObjectForKey(
1111 new NSArray( (Collection) deletedIDsBuffer ),
1112
1113 EOObjectStore.DeletedKey );
1114 storeInfo.setObjectForKey(
1115 new NSArray( (Collection) invalidatedIDsBuffer ),
1116 EOObjectStore.InvalidatedKey );
1117 }
1118
1119
1120
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
1136 EOObjectStore.InsertedKey );
1137 contextInfo.setObjectForKey(
1138 new NSArray( (Collection) updatedObjectsBuffer ),
1139
1140 EOObjectStore.UpdatedKey );
1141 contextInfo.setObjectForKey(
1142 new NSArray( (Collection) deletedObjectsBuffer ),
1143
1144 EOObjectStore.DeletedKey );
1145 contextInfo.setObjectForKey(
1146 new NSArray( (Collection) invalidatedObjectsBuffer ),
1147 EOObjectStore.InvalidatedKey );
1148 }
1149
1150
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
1168
1169 insertedObjectsBuffer.removeAllObjects();
1170 updatedObjectsBuffer.removeAllObjects();
1171 deletedObjectsBuffer.removeAllObjects();
1172 deletedIDsBuffer.removeAllObjects();
1173 invalidatedObjectsBuffer.removeAllObjects();
1174 invalidatedIDsBuffer.removeAllObjects();
1175
1176
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
1217 boolean inserted = false;
1218 boolean updated = false;
1219 boolean deleted = false;
1220
1221
1222 EOGlobalID existingID = globalIDForObject( anObject );
1223 if ( existingID != null )
1224 {
1225
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
1234 forgetObject( anObject );
1235 }
1236
1237
1238 Object existingObject = objectForGlobalID( aGlobalID );
1239 if ( existingObject != null )
1240 {
1241
1242 forgetObject( existingObject );
1243 }
1244
1245 registrar.registerObject( anObject, aGlobalID );
1246
1247
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
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
1290 int i = updatedObjects.indexOfIdenticalObject( anObject );
1291 if ( i != NSArray.NotFound )
1292 {
1293 updatedObjects.removeObjectAtIndex( i );
1294 }
1295
1296
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
1309
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;
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
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
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
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
1434 insertedObjects.removeAllObjects();
1435 deletedObjects.removeAllObjects();
1436 updatedObjects.removeAllObjects();
1437
1438
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
1466 willChange();
1467
1468
1469 processRecentChanges();
1470
1471
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
1484 fireWillSaveChanges();
1485
1486
1487 notifyDelegate(
1488 "editingContextWillSaveChanges",
1489 new Class[] { EOEditingContext.class },
1490 new Object[] { this } );
1491
1492
1493 isInvalidating = true;
1494 try
1495 {
1496
1497 parentStore.saveChangesInEditingContext( this );
1498 }
1499 catch ( RuntimeException e )
1500 {
1501
1502 isInvalidating = false;
1503 throw e;
1504 }
1505 isInvalidating = false;
1506
1507
1508
1509 Object o, key;
1510 Iterator it;
1511
1512
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
1529 updatedObjects.removeAllObjects();
1530 insertedObjects.removeAllObjects();
1531 it = new NSArray( deletedObjects() ).iterator();
1532 while ( it.hasNext() )
1533 {
1534 forgetObject( it.next() );
1535 }
1536
1537
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
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
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
1574 {
1575 copy( aContext, o, this, localVersion );
1576
1577 }
1578
1579 deleteObject( localVersion );
1580 }
1581
1582
1583 List inserts = new NSArray( aContext.insertedObjects() );
1584 it = inserts.iterator();
1585 while ( it.hasNext() )
1586 {
1587 o = it.next();
1588
1589 EOGlobalID id = aContext.globalIDForObject( o );
1590 willChange();
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
1599 insertedObjects.addObject( localVersion );
1600 insertedObjectsBuffer.addObject( localVersion );
1601 }
1602
1603
1604 List updates = new NSArray( aContext.updatedObjects() );
1605 it = updates.iterator();
1606 while ( it.hasNext() )
1607 {
1608 willChange();
1609 o = it.next();
1610 EOGlobalID id = aContext.globalIDForObject( o );
1611 Object localVersion = objectForGlobalID( id );
1612 if ( localVersion == null )
1613 {
1614
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
1625 insertedObjects.addObject( localVersion );
1626 insertedObjectsBuffer.addObject( localVersion );
1627 }
1628 else
1629 {
1630
1631 updatedObjects.addObject( localVersion );
1632
1633
1634 if ( deletedObjectsBuffer.indexOfIdenticalObject(
1635 localVersion ) == NSArray.NotFound )
1636 {
1637 updatedObjectsBuffer.addObject( localVersion );
1638 }
1639 }
1640 }
1641 else
1642 {
1643 copy( aContext, o, this, localVersion );
1644
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
1672
1673
1674
1675
1676
1677
1678
1679
1680
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
1719
1720
1721
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
1743
1744
1745
1746
1747
1748
1749
1750
1751
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
1770 throw new UnsupportedOperationException("Not implemented yet.");
1771 }
1772
1773
1774
1775
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
1811 }
1812 catch ( Exception exc )
1813 {
1814
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 {
1834
1835 willChange();
1836 if ( InvalidatedAllObjectsInStoreNotification
1837 .equals( aNotification.name() ) )
1838 {
1839 refaultObjects();
1840
1841
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
1853
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
1865 recordObject( o, (EOGlobalID) userInfo.objectForKey( id ) );
1866 }
1867 }
1868 }
1869 else
1870 if ( EOObjectStore.ObjectsChangedInStoreNotification
1871 .equals( aNotification.name() ) )
1872 {
1873
1874
1875 willChange();
1876
1877 Object o;
1878 EOGlobalID id;
1879 Enumeration e;
1880 NSDictionary userInfo = aNotification.userInfo();
1881
1882
1883
1884
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
1895 forgetObject( o );
1896 deletedObjectsBuffer.addObject( o );
1897 deletedIDsBuffer.addObject( id );
1898 }
1899 }
1900
1901
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
1912 if ( updatedObjects
1913 .indexOfIdenticalObject( o ) == NSArray.NotFound )
1914 {
1915 refaultObject( o, id, this );
1916 updatedObjectsBuffer.addObject( o );
1917 }
1918 else
1919 {
1920
1921 handleUpdateConflict( id, o );
1922 }
1923 }
1924 }
1925
1926
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
1937 .indexOfIdenticalObject( o ) == NSArray.NotFound )
1938 {
1939 refaultObject( o, id, this );
1940 }
1941 else
1942 {
1943
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
1973
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
1990 mergeExternalChanges( anID, anObject );
1991 }
1992 else
1993 {
1994
1995 }
1996
1997
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
2022 Map fetchSnapshot = committedSnapshotForObject( anObject );
2023
2024
2025 Map currentSnapshot = currentEventSnapshotForObject( anObject );
2026
2027
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
2040 ignoreChanges = true;
2041 parentStore.refaultObject( anObject, anID, this );
2042 ignoreChanges = false;
2043
2044
2045 Map updatedSnapshot = convertSnapshotToDictionary( takeSnapshot( anObject ) );
2046
2047
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
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
2073 if ( proceed )
2074 {
2075 KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, currentDiff );
2076 return true;
2077 }
2078 }
2079 catch ( Exception exc )
2080 {
2081
2082 exc.printStackTrace();
2083 }
2084
2085
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
2107 }
2108 catch ( Exception exc )
2109 {
2110
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
2137 }
2138 catch ( Exception exc )
2139 {
2140
2141 System.err.println(
2142 "Error while messaging delegate: " +
2143 delegate + " : " + aMethodName );
2144 exc.printStackTrace();
2145 }
2146
2147 return null;
2148 }
2149
2150
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
2161
2162
2163 willChange();
2164
2165
2166 int i = updatedObjects.indexOfIdenticalObject( anObject );
2167 if ( i == NSArray.NotFound )
2168 {
2169
2170 i = insertedObjects.indexOfIdenticalObject( anObject );
2171 if ( i == NSArray.NotFound )
2172 {
2173 i = deletedObjects.indexOfIdenticalObject( anObject );
2174 if ( i == NSArray.NotFound )
2175 {
2176
2177 updatedObjects.addObject( anObject );
2178
2179
2180 registrar.setCommitSnapshot( anObject, takeSnapshot( anObject ) );
2181 }
2182 }
2183 }
2184
2185
2186 if ( updatedObjectsBuffer.indexOfIdenticalObject( anObject )
2187 == NSArray.NotFound )
2188 {
2189 updatedObjectsBuffer.addObject( anObject );
2190 }
2191 }
2192
2193
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
2212
2213
2214
2215
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
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
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
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 {
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
2315 NSDictionary values = convertSnapshotToDictionary( aSnapshot );
2316
2317
2318
2319 willChange();
2320 KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, values );
2321
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
2331 Object clone = KeyValueCodingUtilities.thaw( aSnapshot, this, true );
2332
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 {
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
2374
2375
2376 Object clone = KeyValueCodingUtilities.thaw(
2377 KeyValueCodingUtilities.freeze(
2378 aSource, aSourceContext, aSource, false ),
2379 aDestinationContext, false );
2380
2381 aDestinationContext.recordObject( clone, aGlobalID );
2382
2383
2384
2385 aDestinationContext.ignoreChanges = true;
2386 copy( aSourceContext, aSource, aDestinationContext, clone );
2387 aDestinationContext.ignoreChanges = false;
2388
2389
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 {
2404 EOObserverCenter.notifyObserversObjectWillChange( aDestination );
2405 KeyValueCodingUtilities.copy( aSourceContext, aSource, aDestinationContext, aDestination );
2406 return aDestination;
2407 }
2408
2409
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
2443 processRecentChanges();
2444 willRunLater = false;
2445 }
2446
2447
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;
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;
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
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
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 {
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
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
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247