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