diff options
Diffstat (limited to 'projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java')
| -rw-r--r-- | projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java | 5547 |
1 files changed, 2467 insertions, 3080 deletions
diff --git a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java index b01727d..3a39035 100644 --- a/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java +++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java @@ -42,3206 +42,2593 @@ import net.wotonomy.foundation.internal.WotonomyException; //import javax.swing.undo.UndoManager; /** -* EOEditingContext provides transactional support for -* fetching, editing, and committing changes made on a -* collection of objects to a parent object store. <br><br> -* -* EOEditingContext is itself a subclass of EOObjectStore, -* and this means that EOEditingContexts can use other -* EOEditingContexts as their parent. However, there -* still must exist an EOObjectStore as the root of the -* editing hierarchy that can maintain persistent state. -* -* @author michael@mpowers.net -* @author $Author: cgruber $ -* @version $Revision: 894 $ -*/ -public class EOEditingContext - extends EOObjectStore - implements EOObserving -{ - /** - * Key for the NSNotification posted after this editing context - * saves changes. Object of the notification will be this editing - * context, and user info will contain InsertedKey, UpdatedKey, - * and DeletedKey (keys are defined in EOObjectStore). - */ - public static final String - EditingContextDidSaveChangesNotification = - "EOEditingContextDidSaveChangesNotification"; - - /** - * Key for the NSNotification posted after this editing context - * observes changes. Object of the notification will be this editing - * context, and user info will contain InsertedKey, UpdatedKey, InvalidatedKey, - * and DeletedKey (keys are defined in EOObjectStore), however - * the objects in the corresponding Lists will be the actual - * objects, not their ids. - */ - public static final String - ObjectsChangedInEditingContextNotification = - "EOObjectsChangedInEditingContextNotification"; - - /** - * The default run loop ordering processes recent changes - * before delayed observers are notified and before dispatching - * the AWT event queue. - */ - public static int - EditingContextFlushChangesRunLoopOrdering = 300000; - - private static NSSelector runLaterSelector = - new NSSelector( "flushRecentChanges", - new Class[] { Object.class } ); - - private static EOObjectStore defaultParentObjectStore = null; - private static double defaultFetchTimestampLag = 0; - private static boolean retainsRegisteredObjects = true; - - private EOObjectStore parentStore; - private WeakReference delegate; - private WeakReference messageHandler; - private List editorSet; - private double fetchTimestamp; - private boolean lockBeforeModify; - private boolean propagateDeletesAfterEvent; - private boolean stopValidationAfterError; - private NSMutableArray insertedObjects; - private NSMutableArray insertedObjectsBuffer; - private NSArray insertedObjectsProxy; - private NSMutableArray updatedObjects; - private NSMutableArray updatedObjectsBuffer; - private NSArray updatedObjectsProxy; - private NSMutableArray deletedObjects; - private NSMutableArray deletedObjectsBuffer; - private NSArray deletedObjectsProxy; - private NSMutableArray deletedIDsBuffer; - private NSMutableArray invalidatedObjectsBuffer; - private NSMutableArray invalidatedIDsBuffer; - private Registrar registrar; + * EOEditingContext provides transactional support for fetching, editing, and + * committing changes made on a collection of objects to a parent object store. + * <br> + * <br> + * + * EOEditingContext is itself a subclass of EOObjectStore, and this means that + * EOEditingContexts can use other EOEditingContexts as their parent. However, + * there still must exist an EOObjectStore as the root of the editing hierarchy + * that can maintain persistent state. + * + * @author michael@mpowers.net + * @author $Author: cgruber $ + * @version $Revision: 894 $ + */ +public class EOEditingContext extends EOObjectStore implements EOObserving { + /** + * Key for the NSNotification posted after this editing context saves changes. + * Object of the notification will be this editing context, and user info will + * contain InsertedKey, UpdatedKey, and DeletedKey (keys are defined in + * EOObjectStore). + */ + public static final String EditingContextDidSaveChangesNotification = "EOEditingContextDidSaveChangesNotification"; + + /** + * Key for the NSNotification posted after this editing context observes + * changes. Object of the notification will be this editing context, and user + * info will contain InsertedKey, UpdatedKey, InvalidatedKey, and DeletedKey + * (keys are defined in EOObjectStore), however the objects in the corresponding + * Lists will be the actual objects, not their ids. + */ + public static final String ObjectsChangedInEditingContextNotification = "EOObjectsChangedInEditingContextNotification"; + + /** + * The default run loop ordering processes recent changes before delayed + * observers are notified and before dispatching the AWT event queue. + */ + public static int EditingContextFlushChangesRunLoopOrdering = 300000; + + private static NSSelector runLaterSelector = new NSSelector("flushRecentChanges", new Class[] { Object.class }); + + private static EOObjectStore defaultParentObjectStore = null; + private static double defaultFetchTimestampLag = 0; + private static boolean retainsRegisteredObjects = true; + + private EOObjectStore parentStore; + private WeakReference delegate; + private WeakReference messageHandler; + private List editorSet; + private double fetchTimestamp; + private boolean lockBeforeModify; + private boolean propagateDeletesAfterEvent; + private boolean stopValidationAfterError; + private NSMutableArray insertedObjects; + private NSMutableArray insertedObjectsBuffer; + private NSArray insertedObjectsProxy; + private NSMutableArray updatedObjects; + private NSMutableArray updatedObjectsBuffer; + private NSArray updatedObjectsProxy; + private NSMutableArray deletedObjects; + private NSMutableArray deletedObjectsBuffer; + private NSArray deletedObjectsProxy; + private NSMutableArray deletedIDsBuffer; + private NSMutableArray invalidatedObjectsBuffer; + private NSMutableArray invalidatedIDsBuffer; + private Registrar registrar; // private UndoManager undoManager; - // so we don't have to trouble EOObserverCenter - private boolean ignoreChanges; - - // for delayed handling of processRecentChanges - private boolean willRunLater; - - // for handling of notifications posted - // while we're in the saveChanges method - private boolean isInvalidating; - - // for i18n or other customization - static protected String MessageChangeConflict = - "Another user changed an object you are editing: "; - - /** - * Default constructor creates a new editing context - * that uses the default object store. If the default - * object store has not been set, an exception is thrown. - */ - public EOEditingContext() - { - this( defaultParentObjectStore() ); - } - - /** - * Creates a new editing context that uses the specified - * object store as its parent object store. - */ - public EOEditingContext( EOObjectStore anObjectStore ) - { - if ( anObjectStore == null ) - { - throw new IllegalArgumentException( - "A parent object store must be specified." ); - } - - parentStore = anObjectStore; - delegate = null; - messageHandler = null; - editorSet = new LinkedList(); - fetchTimestamp = 0; - lockBeforeModify = false; - propagateDeletesAfterEvent = true; - stopValidationAfterError = true; - insertedObjects = new NSMutableArray(); - insertedObjectsBuffer = new NSMutableArray(); - insertedObjectsProxy = NSArray.arrayBackedByList( insertedObjects ); - updatedObjects = new NSMutableArray(); - updatedObjectsBuffer = new NSMutableArray(); - updatedObjectsProxy = NSArray.arrayBackedByList( updatedObjects ); - deletedObjects = new NSMutableArray(); - deletedObjectsBuffer = new NSMutableArray(); - deletedObjectsProxy = NSArray.arrayBackedByList( deletedObjects ); - deletedIDsBuffer = new NSMutableArray(); - invalidatedObjectsBuffer = new NSMutableArray(); - invalidatedIDsBuffer = new NSMutableArray(); - - if ( instancesRetainRegisteredObjects() ) - { - registrar = new Registrar( this ); - } - else - { - registrar = new WeakRegistrar( this ); - } - - ignoreChanges = false; - willRunLater = false; - isInvalidating = false; - - // create undo manager - //TODO: this should be NSUndoManager + // so we don't have to trouble EOObserverCenter + private boolean ignoreChanges; + + // for delayed handling of processRecentChanges + private boolean willRunLater; + + // for handling of notifications posted + // while we're in the saveChanges method + private boolean isInvalidating; + + // for i18n or other customization + static protected String MessageChangeConflict = "Another user changed an object you are editing: "; + + /** + * Default constructor creates a new editing context that uses the default + * object store. If the default object store has not been set, an exception is + * thrown. + */ + public EOEditingContext() { + this(defaultParentObjectStore()); + } + + /** + * Creates a new editing context that uses the specified object store as its + * parent object store. + */ + public EOEditingContext(EOObjectStore anObjectStore) { + if (anObjectStore == null) { + throw new IllegalArgumentException("A parent object store must be specified."); + } + + parentStore = anObjectStore; + delegate = null; + messageHandler = null; + editorSet = new LinkedList(); + fetchTimestamp = 0; + lockBeforeModify = false; + propagateDeletesAfterEvent = true; + stopValidationAfterError = true; + insertedObjects = new NSMutableArray(); + insertedObjectsBuffer = new NSMutableArray(); + insertedObjectsProxy = NSArray.arrayBackedByList(insertedObjects); + updatedObjects = new NSMutableArray(); + updatedObjectsBuffer = new NSMutableArray(); + updatedObjectsProxy = NSArray.arrayBackedByList(updatedObjects); + deletedObjects = new NSMutableArray(); + deletedObjectsBuffer = new NSMutableArray(); + deletedObjectsProxy = NSArray.arrayBackedByList(deletedObjects); + deletedIDsBuffer = new NSMutableArray(); + invalidatedObjectsBuffer = new NSMutableArray(); + invalidatedIDsBuffer = new NSMutableArray(); + + if (instancesRetainRegisteredObjects()) { + registrar = new Registrar(this); + } else { + registrar = new WeakRegistrar(this); + } + + ignoreChanges = false; + willRunLater = false; + isInvalidating = false; + + // create undo manager + // TODO: this should be NSUndoManager // undoManager = new UndoManager(); - // register for notifications - NSSelector handleNotification = - new NSSelector( "handleNotification", - new Class[] { NSNotification.class } ); - // any from parent store - NSNotificationCenter.defaultCenter().addObserver( - this, handleNotification, null, parentStore ); - // global id change from any - NSNotificationCenter.defaultCenter().addObserver( - this, handleNotification, EOGlobalID.GlobalIDChangedNotification, null ); + // register for notifications + NSSelector handleNotification = new NSSelector("handleNotification", new Class[] { NSNotification.class }); + // any from parent store + NSNotificationCenter.defaultCenter().addObserver(this, handleNotification, null, parentStore); + // global id change from any + NSNotificationCenter.defaultCenter().addObserver(this, handleNotification, + EOGlobalID.GlobalIDChangedNotification, null); //new net.wotonomy.ui.swing.NotificationInspector( null, parentStore ); - } - - /** - * Registers the specified object as an editor for this - * context. The object is expected to implement - * EOEditingContext.Editor. - */ - public void addEditor ( Object anEditor ) - { - if ( anEditor == null ) return; - editorSet.add( new WeakReference( anEditor ) ); - } - - /** - * Returns a read-only List of objects associated with the object - * with the specified id for the specified property - * relationship, or may return a placeholder array that - * will defer the fetch until needed (aka an array fault). - * All objects must be registered in the specified editing context. - * This implementation calls to its parent object store's - * implementation if the requested source object is not - * registered in this editing context. - * The specified relationship key must produce a result of - * type Collection for the source object or an exception is thrown. - */ - public NSArray arrayFaultWithSourceGlobalID ( - EOGlobalID aGlobalID, - String aRelationshipKey, - EOEditingContext aContext ) - { - NSArray result = null; - Object source = registrar.objectForGlobalID( aGlobalID ); - - // if not registered in our context - if ( source == null ) - { - // get the object registered into our context - result = parentStore.arrayFaultWithSourceGlobalID( - aGlobalID, aRelationshipKey, this ); - } - else // source is registered in our context - { - // get existing value - Object value; - if ( source instanceof EOKeyValueCoding ) - { - value = ((EOKeyValueCoding)source).storedValueForKey( - aRelationshipKey ); - } - else // handle directly - { - value = EOKeyValueCodingSupport.storedValueForKey( - source, aRelationshipKey ); - } - - if ( value == null ) - { - // do the same as if the source was null - result = parentStore.arrayFaultWithSourceGlobalID( - aGlobalID, aRelationshipKey, this ); - } - else - if ( value instanceof NSArray ) - { - result = (NSArray) value; - } - else // not NSArray - if ( value instanceof Collection ) - { - // convert to NSArray - result = new NSArray( (Collection) value ); - } - else - { - throw new WotonomyException( - "Relationship key did not return a collection: " - + aGlobalID + " : " + aRelationshipKey ); - } - } - - // if our context is not the specified context - if ( aContext != this ) - { - result = (NSArray) clone( this, result, aContext ); - } - - return result; - } - - /** - * Returns a snapshot of the specified object as it - * existed when it was last read or committed to the - * parent object store. - */ - public NSDictionary committedSnapshotForObject ( - Object anObject ) - { - byte[] snapshot = (byte[]) - registrar.getCommitSnapshot( anObject ); - if ( snapshot == null ) - { - // this object not modified: take a current snapshot - snapshot = takeSnapshot( anObject ); - } - return convertSnapshotToDictionary( snapshot ); - } - - /** - * Returns a snapshot of the specified object as it - * existed before the edits triggered by the current - * event loop were processed. - */ - public NSDictionary currentEventSnapshotForObject ( - Object anObject ) - { - byte[] result = (byte[]) - registrar.getCurrentSnapshot( anObject ); - if ( result == null ) - { - return committedSnapshotForObject( anObject ); - } - return convertSnapshotToDictionary( result ); - } - - /** - * Returns the delegate for this editing context, - * or null if no delegate has been set. - */ - public Object delegate () - { - if ( delegate == null ) return null; - return delegate.get(); - } - - /** - * Deletes the specified object from this editing context. - * The editing context marks the object as deleted and - * will notify the parent store when changes are committed. - */ - public void deleteObject ( - Object anObject ) - { - willChange(); + } + + /** + * Registers the specified object as an editor for this context. The object is + * expected to implement EOEditingContext.Editor. + */ + public void addEditor(Object anEditor) { + if (anEditor == null) + return; + editorSet.add(new WeakReference(anEditor)); + } + + /** + * Returns a read-only List of objects associated with the object with the + * specified id for the specified property relationship, or may return a + * placeholder array that will defer the fetch until needed (aka an array + * fault). All objects must be registered in the specified editing context. This + * implementation calls to its parent object store's implementation if the + * requested source object is not registered in this editing context. The + * specified relationship key must produce a result of type Collection for the + * source object or an exception is thrown. + */ + public NSArray arrayFaultWithSourceGlobalID(EOGlobalID aGlobalID, String aRelationshipKey, + EOEditingContext aContext) { + NSArray result = null; + Object source = registrar.objectForGlobalID(aGlobalID); + + // if not registered in our context + if (source == null) { + // get the object registered into our context + result = parentStore.arrayFaultWithSourceGlobalID(aGlobalID, aRelationshipKey, this); + } else // source is registered in our context + { + // get existing value + Object value; + if (source instanceof EOKeyValueCoding) { + value = ((EOKeyValueCoding) source).storedValueForKey(aRelationshipKey); + } else // handle directly + { + value = EOKeyValueCodingSupport.storedValueForKey(source, aRelationshipKey); + } + + if (value == null) { + // do the same as if the source was null + result = parentStore.arrayFaultWithSourceGlobalID(aGlobalID, aRelationshipKey, this); + } else if (value instanceof NSArray) { + result = (NSArray) value; + } else // not NSArray + if (value instanceof Collection) { + // convert to NSArray + result = new NSArray((Collection) value); + } else { + throw new WotonomyException( + "Relationship key did not return a collection: " + aGlobalID + " : " + aRelationshipKey); + } + } + + // if our context is not the specified context + if (aContext != this) { + result = (NSArray) clone(this, result, aContext); + } + + return result; + } + + /** + * Returns a snapshot of the specified object as it existed when it was last + * read or committed to the parent object store. + */ + public NSDictionary committedSnapshotForObject(Object anObject) { + byte[] snapshot = (byte[]) registrar.getCommitSnapshot(anObject); + if (snapshot == null) { + // this object not modified: take a current snapshot + snapshot = takeSnapshot(anObject); + } + return convertSnapshotToDictionary(snapshot); + } + + /** + * Returns a snapshot of the specified object as it existed before the edits + * triggered by the current event loop were processed. + */ + public NSDictionary currentEventSnapshotForObject(Object anObject) { + byte[] result = (byte[]) registrar.getCurrentSnapshot(anObject); + if (result == null) { + return committedSnapshotForObject(anObject); + } + return convertSnapshotToDictionary(result); + } + + /** + * Returns the delegate for this editing context, or null if no delegate has + * been set. + */ + public Object delegate() { + if (delegate == null) + return null; + return delegate.get(); + } + + /** + * Deletes the specified object from this editing context. The editing context + * marks the object as deleted and will notify the parent store when changes are + * committed. + */ + public void deleteObject(Object anObject) { + willChange(); int i; // remove from added objects if necessary - i = insertedObjects.indexOfIdenticalObject( anObject ); - if ( i != NSArray.NotFound ) + i = insertedObjects.indexOfIdenticalObject(anObject); + if (i != NSArray.NotFound) { + insertedObjects.removeObjectAtIndex(i); + + // if in the inserted objects buffer + int index = insertedObjectsBuffer.indexOfIdenticalObject(anObject); + if (index != NSArray.NotFound) { + // remove from inserted objects buffer + insertedObjectsBuffer.removeObjectAtIndex(index); + } + + // now forget the object ever existed. + forgetObject(anObject); + + // we're done + return; + } else // otherwise add to deleted objects list { - insertedObjects.removeObjectAtIndex( i ); - - // if in the inserted objects buffer - int index = insertedObjectsBuffer.indexOfIdenticalObject( anObject ); - if ( index != NSArray.NotFound ) - { - // remove from inserted objects buffer - insertedObjectsBuffer.removeObjectAtIndex( index ); - } - - // now forget the object ever existed. - forgetObject( anObject ); - - // we're done - return; - } - else // otherwise add to deleted objects list - { - deletedObjects.addObject( anObject ); + deletedObjects.addObject(anObject); } - + // remove from updated objects if necessary - i = updatedObjects.indexOfIdenticalObject( anObject ); - if ( i != NSArray.NotFound ) - { - updatedObjects.removeObjectAtIndex( i ); - } - - // add to buffer - deletedObjectsBuffer.addObject( anObject ); - deletedIDsBuffer.addObject( globalIDForObject( anObject ) ); - } - - /** - * Returns a read-only List of all objects marked as deleted - * in this editing context. - */ - public NSArray deletedObjects () - { - return deletedObjectsProxy; - } - - /** - * Called by child editing contexts when they no longer - * need to track the specified id. - * This implementation forwards the call to the parent store. - */ - public void editingContextDidForgetObjectWithGlobalID ( - EOEditingContext aContext, - EOGlobalID aGlobalID ) - { - parentStore.editingContextDidForgetObjectWithGlobalID( - aContext, aGlobalID ); - } - - /** - * Returns a read-only List of registered editors of this - * editing context. - */ - public NSArray editors () - { - NSMutableArray result = new NSMutableArray(); - Object o; - Iterator i = editorSet.iterator(); - while ( i.hasNext() ) - { - o = ((WeakReference)i.next()).get(); - if ( o != null ) - { - result.addObject( o ); - } - else - { - i.remove(); - } - } - return result; - } - -/* - public static void encodeObjectWithCoder ( - Object anObject, - NSCoder aCoder ) - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ + i = updatedObjects.indexOfIdenticalObject(anObject); + if (i != NSArray.NotFound) { + updatedObjects.removeObjectAtIndex(i); + } - /** - * Returns the object for the specified id. - * If the object is registered in in this context - * but not in the specified context, - * this implementation will create a copy of the object - * and register it in the specified context. - * Otherwise it will forward the call to the parent - * object store. - */ - public /*EOEnterpriseObject*/ Object faultForGlobalID ( - EOGlobalID aGlobalID, - EOEditingContext aContext ) - { - Object result = registrar.objectForGlobalID( aGlobalID ); - - // if not registered in our context - if ( result == null ) - { - // get the object registered into our context - result = parentStore.faultForGlobalID( aGlobalID, this ); - } - - // if our context is not the specified context - if ( aContext != this ) - { - result = registerClone( aGlobalID, this, result, aContext ); - } - - return result; - } - - /** - * Returns a fault representing an object of - * the specified entity type with values from - * the specified dictionary. - * This implementation calls faultForRawRow - * on the parent store. - */ - public Object faultForRawRow ( - Map aDictionary, - String anEntityName ) - { - return parentStore.faultForRawRow( - aDictionary, anEntityName, this ); - } - - /** - * Returns a fault representing an object of - * the specified entity type with values from - * the specified dictionary. The fault should - * belong to the specified editing context. - * This implementation forwards the call to - * the parent store. - */ - public /*EOEnterpriseObject*/ Object faultForRawRow ( - Map aDictionary, - String anEntityName, - EOEditingContext aContext ) - { - return parentStore.faultForRawRow( - aDictionary, anEntityName, aContext ); - } - - /** - * Returns the fetch timestamp for this editing context. - */ - public double fetchTimestamp () - { - return fetchTimestamp; - } - - /** - * Unregisters the specified object from this editing context, - * removing all references to it. Use this method to remove - * an object from the context without marking it for deletion. - */ - public void forgetObject ( - Object anObject ) - { - EOGlobalID id = registrar.globalIDForObject( anObject ); - if ( id == null ) - { - System.err.println( - "EOEditingContext.forgetObject: not registered: " + anObject ); - return; - } - - // unregister object - registrar.forgetObject( anObject ); - - // remove from all, inserted, updated, and deleted lists - int index; - index = updatedObjects.indexOfIdenticalObject( anObject ); - if ( index != NSArray.NotFound ) - { - updatedObjects.removeObjectAtIndex( index ); - } - index = insertedObjects.indexOfIdenticalObject( anObject ); - if ( index != NSArray.NotFound ) - { - insertedObjects.removeObjectAtIndex( index ); - return; - } - index = deletedObjects.indexOfIdenticalObject( anObject ); - if ( index != NSArray.NotFound ) - { - deletedObjects.removeObjectAtIndex( index ); - return; - } - - // notify parent context - parentStore.editingContextDidForgetObjectWithGlobalID( this, id ); - } - - /** - * Returns the id for the specified object, or null - * if the object is not registered in this context. - */ - public EOGlobalID globalIDForObject ( - Object anObject ) - { - return registrar.globalIDForObject( anObject ); - } - - /** - * Returns an array of ids for an array of objects. - */ - private NSArray globalIDsForObjects( - List anObjectList ) - { - NSMutableArray result = new NSMutableArray(); - Iterator it = anObjectList.iterator(); - while ( it.hasNext() ) - { - result.add( globalIDForObject( it.next() ) ); - } - return result; - } - - /** - * Returns whether this editing context has changes that - * have not yet been committed to the parent object store. - */ - public boolean hasChanges () - { - if ( updatedObjects.count() > 0 ) return true; - if ( insertedObjects.count() > 0 ) return true; - if ( deletedObjects.count() > 0 ) return true; - return false; - } - - /** - * Given a newly instantiated object, this method - * initializes its properties to values appropriate - * for the specified id. The object should already - * belong to the specified editing context. - * This method is called to populate faults. - * This implementation will try to apply the values - * from an object with a matching id in this editing - * context if possible, calling to the parent object - * store only if such an object is not found. - */ - public void initializeObject ( - /*EOEnterpriseObject*/ Object anObject, - EOGlobalID aGlobalID, - EOEditingContext aContext ) - { - Object existingObject = registrar.objectForGlobalID( aGlobalID ); - - // if not registered in our context - if ( existingObject == null ) - { - // get the object registered into our context - existingObject = parentStore.faultForGlobalID( aGlobalID, this ); - } - - if ( aContext == this ) - { - // initialize the object - parentStore.initializeObject( - /*(EOEnterpriseObject)*/existingObject, aGlobalID, this ); - } - else // ( aContext != this ) - { - // translates child relationships - copy( this, existingObject, aContext, anObject ); - } - - aContext.registrar.setCommitSnapshot( anObject, null ); - aContext.registrar.setCurrentSnapshot( anObject, null ); - } - - /** - * Inserts the specified object into this editing context. - * This implementation calls insertObjectWithGlobalID - * with an EOTemporaryGlobalID. - */ - public void insertObject ( Object anObject ) - { - insertObjectWithGlobalID( - anObject, new EOTemporaryGlobalID() ); - } - - /** - * Inserts the specified object into this editing context - * with the specified id, which is expected to be a - * temporary id. - */ - public void insertObjectWithGlobalID ( - Object anObject, - EOGlobalID aGlobalID ) - { - willChange(); - - // if this object was marked for deletion - int index = deletedObjects.indexOfIdenticalObject( anObject ); - if ( index != NSArray.NotFound ) - { - // don't need to re-register: just update the lists - - // remove from deleted list - deletedObjects.removeObjectAtIndex( index ); - - // if in the deleted ids buffer - index = deletedIDsBuffer.indexOfIdenticalObject( anObject ); - if ( index != NSArray.NotFound ) - { - // remove from deleted ids buffer - deletedIDsBuffer.removeObjectAtIndex( index ); - } - - // if in the deleted objects buffer - index = deletedObjectsBuffer.indexOfIdenticalObject( anObject ); - if ( index != NSArray.NotFound ) - { - // remove from deleted objects buffer - deletedObjectsBuffer.removeObjectAtIndex( index ); - } - else // not in the deleted objects buffer - { - // add to the inserted objects buffer - insertedObjectsBuffer.addObject( anObject ); - } - - // we're done - return; - } - - // make sure object is not already in editing context - if ( objectForGlobalID( aGlobalID ) != null ) - { - throw new WotonomyException( - "Tried to insert but object was already registered:" - + aGlobalID ); - } - - // register object - recordObject( anObject, aGlobalID ); - - // add to inserted list - insertedObjects.addObject( anObject ); - // add to buffer - insertedObjectsBuffer.addObject( anObject ); - } - - /** - * Returns a read-only List of the objects that have been - * inserted into this editing context. - */ - public NSArray insertedObjects () - { - return insertedObjectsProxy; - } - - /** - * Turn all objects in this editing context into faults, - * so that they will be fetched the next time they are - * accessed, and calls invalidateObjectsWithGlobalIDs - * on the parent object store. - */ - public void invalidateAllObjects () - { - // register change so processRecentChanges is called - willChange(); - - invalidateAllObjectsQuietly(); - - // post notification - NSNotificationCenter.defaultCenter().postNotification( - new NSNotification( - InvalidatedAllObjectsInStoreNotification, this ) ); - } - - /** - * Only refaults all objects, does not notify will change - * nor post notification, but does call parent store. - * Called by invalidateAllObjects() and handleNotification(). - */ - private void invalidateAllObjectsQuietly() - { - // remember the ids - NSMutableArray ids = new NSMutableArray( registrar.registeredGlobalIDs() ); - - // track of discarded IDs (from inserted objects) - NSMutableArray discardedIDs = new NSMutableArray(); - - // refault all objects - EOGlobalID id; - Object o; - Enumeration e = ids.objectEnumerator(); - while ( e.hasMoreElements() ) - { - id = (EOGlobalID) e.nextElement(); - o = objectForGlobalID( id ); - - // some objects may have been manually discarded - if ( o != null ) - { - // don't refault newly inserted objects - if ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) - { - refaultObject( o, id, this ); - } - else - { - // discard inserted objects - forgetObject( o ); - discardedIDs.add( id ); - } - invalidatedObjectsBuffer.add( o ); - } - invalidatedIDsBuffer.add( id ); - } - ids.removeAll( discardedIDs ); - - // call to parent store (should call this after posting instead?) - isInvalidating = true; - parentStore.invalidateObjectsWithGlobalIDs( ids ); - isInvalidating = false; - } - - /** - * Turns the objects with the specified ids into faults, - * so that they will be fetched the next time they are - * accessed, and forwards the call to the parent object store. - */ - public void invalidateObjectsWithGlobalIDs ( - List anArray ) - { - // register change so processRecentChanges is called - willChange(); - - // call to parent to invalidate objects - parentStore.invalidateObjectsWithGlobalIDs( anArray ); - - Object o; - EOGlobalID id; - Iterator it = anArray.iterator(); - while ( it.hasNext() ) - { - id = (EOGlobalID) it.next(); - if ( id != null ) - { - o = objectForGlobalID( id ); - if ( o != null ) - { - Object result = notifyDelegate( - "editingContextShouldInvalidateObject", - new Class[] { EOEditingContext.class, Object.class, EOGlobalID.class }, - new Object[] { this, o, id } ); - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - // refault the object - refaultObject( o, id, this ); - invalidatedObjectsBuffer.add( o ); - invalidatedIDsBuffer.add( id ); - } - } - } - else - { - throw new WotonomyException( - "Attempted to invalidate a null global id: " + anArray ); - } - } - - } -/* - public boolean invalidatesObjectsWhenFinalized ( ) - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } - - public boolean invalidatesObjectsWhenFreed ( ) - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ - /** - * Returns whether the object referenced by the - * specified id is locked. - * This implementation simply forwards the call to - * the parent object store. - */ - public boolean isObjectLockedWithGlobalID ( - EOGlobalID aGlobalID, - EOEditingContext aContext) - { - return parentStore.isObjectLockedWithGlobalID( - aGlobalID, aContext ); - } - -/* - public void lock () - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ - /** - * Locks the specified object in this editing context - * by calling lockObjectWithGlobalID on the parent store. - */ - public void lockObject ( - Object anObject ) - { - parentStore.lockObjectWithGlobalID( - globalIDForObject( anObject ), this ); - } - - /** - * Locks the object referenced by the specified id - * in the specified editing context. - * This implementation simply forwards the call to - * the parent object store. - */ - public void lockObjectWithGlobalID ( - EOGlobalID aGlobalID, - EOEditingContext aContext) - { - parentStore.lockObjectWithGlobalID( - aGlobalID, aContext ); - } - - /** - * Returns whether this editing context attempts to - * lock objects when they are first modified. - */ - public boolean locksObjectsBeforeFirstModification () - { - return lockBeforeModify; - } - - /** - * Returns the message handler for this editing context, - * or null if no message handler has been set. - */ - public Object messageHandler () - { - if ( messageHandler == null ) return null; - return messageHandler.get(); - } - - /** - * Returns the object registered in this editing context - * for the specified id, or null if that id is not - * registered. - */ - public Object objectForGlobalID ( - EOGlobalID aGlobalID ) - { - return registrar.objectForGlobalID( aGlobalID ); - } - - /** - * Returns a read-only List of objects associated with the object - * with the specified id for the specified property - * relationship. This method may not return an array fault - * because array faults call this method to fetch on demand. - * All objects must be registered the specified editing context. - * The specified relationship key must produce a result of - * type Collection for the source object or an exception is thrown. - */ - public NSArray objectsForSourceGlobalID ( - EOGlobalID aGlobalID, - String aRelationshipKey, - EOEditingContext aContext ) - { + // add to buffer + deletedObjectsBuffer.addObject(anObject); + deletedIDsBuffer.addObject(globalIDForObject(anObject)); + } + + /** + * Returns a read-only List of all objects marked as deleted in this editing + * context. + */ + public NSArray deletedObjects() { + return deletedObjectsProxy; + } + + /** + * Called by child editing contexts when they no longer need to track the + * specified id. This implementation forwards the call to the parent store. + */ + public void editingContextDidForgetObjectWithGlobalID(EOEditingContext aContext, EOGlobalID aGlobalID) { + parentStore.editingContextDidForgetObjectWithGlobalID(aContext, aGlobalID); + } + + /** + * Returns a read-only List of registered editors of this editing context. + */ + public NSArray editors() { + NSMutableArray result = new NSMutableArray(); + Object o; + Iterator i = editorSet.iterator(); + while (i.hasNext()) { + o = ((WeakReference) i.next()).get(); + if (o != null) { + result.addObject(o); + } else { + i.remove(); + } + } + return result; + } + + /* + * public static void encodeObjectWithCoder ( Object anObject, NSCoder aCoder ) + * { throw new net.wotonomy.util.WotonomyException("Not implemented yet."); } + */ + + /** + * Returns the object for the specified id. If the object is registered in in + * this context but not in the specified context, this implementation will + * create a copy of the object and register it in the specified context. + * Otherwise it will forward the call to the parent object store. + */ + public /* EOEnterpriseObject */ Object faultForGlobalID(EOGlobalID aGlobalID, EOEditingContext aContext) { + Object result = registrar.objectForGlobalID(aGlobalID); + + // if not registered in our context + if (result == null) { + // get the object registered into our context + result = parentStore.faultForGlobalID(aGlobalID, this); + } + + // if our context is not the specified context + if (aContext != this) { + result = registerClone(aGlobalID, this, result, aContext); + } + + return result; + } + + /** + * Returns a fault representing an object of the specified entity type with + * values from the specified dictionary. This implementation calls + * faultForRawRow on the parent store. + */ + public Object faultForRawRow(Map aDictionary, String anEntityName) { + return parentStore.faultForRawRow(aDictionary, anEntityName, this); + } + + /** + * Returns a fault representing an object of the specified entity type with + * values from the specified dictionary. The fault should belong to the + * specified editing context. This implementation forwards the call to the + * parent store. + */ + public /* EOEnterpriseObject */ Object faultForRawRow(Map aDictionary, String anEntityName, + EOEditingContext aContext) { + return parentStore.faultForRawRow(aDictionary, anEntityName, aContext); + } + + /** + * Returns the fetch timestamp for this editing context. + */ + public double fetchTimestamp() { + return fetchTimestamp; + } + + /** + * Unregisters the specified object from this editing context, removing all + * references to it. Use this method to remove an object from the context + * without marking it for deletion. + */ + public void forgetObject(Object anObject) { + EOGlobalID id = registrar.globalIDForObject(anObject); + if (id == null) { + System.err.println("EOEditingContext.forgetObject: not registered: " + anObject); + return; + } + + // unregister object + registrar.forgetObject(anObject); + + // remove from all, inserted, updated, and deleted lists + int index; + index = updatedObjects.indexOfIdenticalObject(anObject); + if (index != NSArray.NotFound) { + updatedObjects.removeObjectAtIndex(index); + } + index = insertedObjects.indexOfIdenticalObject(anObject); + if (index != NSArray.NotFound) { + insertedObjects.removeObjectAtIndex(index); + return; + } + index = deletedObjects.indexOfIdenticalObject(anObject); + if (index != NSArray.NotFound) { + deletedObjects.removeObjectAtIndex(index); + return; + } + + // notify parent context + parentStore.editingContextDidForgetObjectWithGlobalID(this, id); + } + + /** + * Returns the id for the specified object, or null if the object is not + * registered in this context. + */ + public EOGlobalID globalIDForObject(Object anObject) { + return registrar.globalIDForObject(anObject); + } + + /** + * Returns an array of ids for an array of objects. + */ + private NSArray globalIDsForObjects(List anObjectList) { + NSMutableArray result = new NSMutableArray(); + Iterator it = anObjectList.iterator(); + while (it.hasNext()) { + result.add(globalIDForObject(it.next())); + } + return result; + } + + /** + * Returns whether this editing context has changes that have not yet been + * committed to the parent object store. + */ + public boolean hasChanges() { + if (updatedObjects.count() > 0) + return true; + if (insertedObjects.count() > 0) + return true; + if (deletedObjects.count() > 0) + return true; + return false; + } + + /** + * Given a newly instantiated object, this method initializes its properties to + * values appropriate for the specified id. The object should already belong to + * the specified editing context. This method is called to populate faults. This + * implementation will try to apply the values from an object with a matching id + * in this editing context if possible, calling to the parent object store only + * if such an object is not found. + */ + public void initializeObject(/* EOEnterpriseObject */ Object anObject, EOGlobalID aGlobalID, + EOEditingContext aContext) { + Object existingObject = registrar.objectForGlobalID(aGlobalID); + + // if not registered in our context + if (existingObject == null) { + // get the object registered into our context + existingObject = parentStore.faultForGlobalID(aGlobalID, this); + } + + if (aContext == this) { + // initialize the object + parentStore.initializeObject(/* (EOEnterpriseObject) */existingObject, aGlobalID, this); + } else // ( aContext != this ) + { + // translates child relationships + copy(this, existingObject, aContext, anObject); + } + + aContext.registrar.setCommitSnapshot(anObject, null); + aContext.registrar.setCurrentSnapshot(anObject, null); + } + + /** + * Inserts the specified object into this editing context. This implementation + * calls insertObjectWithGlobalID with an EOTemporaryGlobalID. + */ + public void insertObject(Object anObject) { + insertObjectWithGlobalID(anObject, new EOTemporaryGlobalID()); + } + + /** + * Inserts the specified object into this editing context with the specified id, + * which is expected to be a temporary id. + */ + public void insertObjectWithGlobalID(Object anObject, EOGlobalID aGlobalID) { + willChange(); + + // if this object was marked for deletion + int index = deletedObjects.indexOfIdenticalObject(anObject); + if (index != NSArray.NotFound) { + // don't need to re-register: just update the lists + + // remove from deleted list + deletedObjects.removeObjectAtIndex(index); + + // if in the deleted ids buffer + index = deletedIDsBuffer.indexOfIdenticalObject(anObject); + if (index != NSArray.NotFound) { + // remove from deleted ids buffer + deletedIDsBuffer.removeObjectAtIndex(index); + } + + // if in the deleted objects buffer + index = deletedObjectsBuffer.indexOfIdenticalObject(anObject); + if (index != NSArray.NotFound) { + // remove from deleted objects buffer + deletedObjectsBuffer.removeObjectAtIndex(index); + } else // not in the deleted objects buffer + { + // add to the inserted objects buffer + insertedObjectsBuffer.addObject(anObject); + } + + // we're done + return; + } + + // make sure object is not already in editing context + if (objectForGlobalID(aGlobalID) != null) { + throw new WotonomyException("Tried to insert but object was already registered:" + aGlobalID); + } + + // register object + recordObject(anObject, aGlobalID); + + // add to inserted list + insertedObjects.addObject(anObject); + // add to buffer + insertedObjectsBuffer.addObject(anObject); + } + + /** + * Returns a read-only List of the objects that have been inserted into this + * editing context. + */ + public NSArray insertedObjects() { + return insertedObjectsProxy; + } + + /** + * Turn all objects in this editing context into faults, so that they will be + * fetched the next time they are accessed, and calls + * invalidateObjectsWithGlobalIDs on the parent object store. + */ + public void invalidateAllObjects() { + // register change so processRecentChanges is called + willChange(); + + invalidateAllObjectsQuietly(); + + // post notification + NSNotificationCenter.defaultCenter() + .postNotification(new NSNotification(InvalidatedAllObjectsInStoreNotification, this)); + } + + /** + * Only refaults all objects, does not notify will change nor post notification, + * but does call parent store. Called by invalidateAllObjects() and + * handleNotification(). + */ + private void invalidateAllObjectsQuietly() { + // remember the ids + NSMutableArray ids = new NSMutableArray(registrar.registeredGlobalIDs()); + + // track of discarded IDs (from inserted objects) + NSMutableArray discardedIDs = new NSMutableArray(); + + // refault all objects + EOGlobalID id; + Object o; + Enumeration e = ids.objectEnumerator(); + while (e.hasMoreElements()) { + id = (EOGlobalID) e.nextElement(); + o = objectForGlobalID(id); + + // some objects may have been manually discarded + if (o != null) { + // don't refault newly inserted objects + if (insertedObjects.indexOfIdenticalObject(o) == NSArray.NotFound) { + refaultObject(o, id, this); + } else { + // discard inserted objects + forgetObject(o); + discardedIDs.add(id); + } + invalidatedObjectsBuffer.add(o); + } + invalidatedIDsBuffer.add(id); + } + ids.removeAll(discardedIDs); + + // call to parent store (should call this after posting instead?) + isInvalidating = true; + parentStore.invalidateObjectsWithGlobalIDs(ids); + isInvalidating = false; + } + + /** + * Turns the objects with the specified ids into faults, so that they will be + * fetched the next time they are accessed, and forwards the call to the parent + * object store. + */ + public void invalidateObjectsWithGlobalIDs(List anArray) { + // register change so processRecentChanges is called + willChange(); + + // call to parent to invalidate objects + parentStore.invalidateObjectsWithGlobalIDs(anArray); + + Object o; + EOGlobalID id; + Iterator it = anArray.iterator(); + while (it.hasNext()) { + id = (EOGlobalID) it.next(); + if (id != null) { + o = objectForGlobalID(id); + if (o != null) { + Object result = notifyDelegate("editingContextShouldInvalidateObject", + new Class[] { EOEditingContext.class, Object.class, EOGlobalID.class }, + new Object[] { this, o, id }); + if ((result == null) || (Boolean.TRUE.equals(result))) { + // refault the object + refaultObject(o, id, this); + invalidatedObjectsBuffer.add(o); + invalidatedIDsBuffer.add(id); + } + } + } else { + throw new WotonomyException("Attempted to invalidate a null global id: " + anArray); + } + } + + } + + /* + * public boolean invalidatesObjectsWhenFinalized ( ) { throw new + * net.wotonomy.util.WotonomyException("Not implemented yet."); } + * + * public boolean invalidatesObjectsWhenFreed ( ) { throw new + * net.wotonomy.util.WotonomyException("Not implemented yet."); } + */ + /** + * Returns whether the object referenced by the specified id is locked. This + * implementation simply forwards the call to the parent object store. + */ + public boolean isObjectLockedWithGlobalID(EOGlobalID aGlobalID, EOEditingContext aContext) { + return parentStore.isObjectLockedWithGlobalID(aGlobalID, aContext); + } + + /* + * public void lock () { throw new + * net.wotonomy.util.WotonomyException("Not implemented yet."); } + */ + /** + * Locks the specified object in this editing context by calling + * lockObjectWithGlobalID on the parent store. + */ + public void lockObject(Object anObject) { + parentStore.lockObjectWithGlobalID(globalIDForObject(anObject), this); + } + + /** + * Locks the object referenced by the specified id in the specified editing + * context. This implementation simply forwards the call to the parent object + * store. + */ + public void lockObjectWithGlobalID(EOGlobalID aGlobalID, EOEditingContext aContext) { + parentStore.lockObjectWithGlobalID(aGlobalID, aContext); + } + + /** + * Returns whether this editing context attempts to lock objects when they are + * first modified. + */ + public boolean locksObjectsBeforeFirstModification() { + return lockBeforeModify; + } + + /** + * Returns the message handler for this editing context, or null if no message + * handler has been set. + */ + public Object messageHandler() { + if (messageHandler == null) + return null; + return messageHandler.get(); + } + + /** + * Returns the object registered in this editing context for the specified id, + * or null if that id is not registered. + */ + public Object objectForGlobalID(EOGlobalID aGlobalID) { + return registrar.objectForGlobalID(aGlobalID); + } + + /** + * Returns a read-only List of objects associated with the object with the + * specified id for the specified property relationship. This method may not + * return an array fault because array faults call this method to fetch on + * demand. All objects must be registered the specified editing context. The + * specified relationship key must produce a result of type Collection for the + * source object or an exception is thrown. + */ + public NSArray objectsForSourceGlobalID(EOGlobalID aGlobalID, String aRelationshipKey, EOEditingContext aContext) { //System.out.println( "EOEditingContext.objectsForSourceGlobalID: " //+ aGlobalID + " : " + aRelationshipKey ); - - NSArray result = null; - -if ( aContext == this ) -{ - throw new WotonomyException( "Assert failed: calling objectsForSourceGlobalID on ourself." ); -} - Object source = registrar.objectForGlobalID( aGlobalID ); - - // if not registered in our context - if ( source == null ) - { - // get the object registered into our context - result = parentStore.objectsForSourceGlobalID( - aGlobalID, aRelationshipKey, this ); - } - else // source is registered in our context - { - // get existing value - Object value; - if ( source instanceof EOKeyValueCoding ) - { - value = ((EOKeyValueCoding)source).storedValueForKey( - aRelationshipKey ); - } - else // handle directly - { - value = EOKeyValueCodingSupport.storedValueForKey( - source, aRelationshipKey ); - } - - // if we don't have a valid value on our object - if ( ( value == null ) - || ( ( value instanceof ArrayFault ) - && ( !((ArrayFault)value).isFetched() ) ) ) - { - // do the same as if the source was null - result = parentStore.objectsForSourceGlobalID( - aGlobalID, aRelationshipKey, this ); - - // set our value since we have it - if ( source instanceof EOKeyValueCoding ) - { - ((EOKeyValueCoding)source).takeStoredValueForKey( - result, aRelationshipKey ); - } - else // handle directly - { - EOKeyValueCodingSupport.takeStoredValueForKey( - source, result, aRelationshipKey ); - } - } - else - if ( ( value instanceof ArrayFault ) - && ( !((ArrayFault)value).isFetched() ) ) - { - // do the same as if the source was null - result = parentStore.objectsForSourceGlobalID( - aGlobalID, aRelationshipKey, this ); - } - else - if ( value instanceof NSArray ) - { - result = (NSArray) value; - } - else // not NSArray - if ( value instanceof Collection ) - { - // convert to NSArray - result = new NSArray( (Collection) value ); - } - else - { - throw new WotonomyException( - "Relationship key did not return a collection: " - + aGlobalID + " : " + aRelationshipKey ); - } - } - - // if our context is not the specified context - if ( aContext != this ) - { - result = (NSArray) clone( this, result, aContext ); - } - - return result; - } - - /** - * Returns a read-only List of objects the meet the criteria of - * the supplied specification. This method simply calls - * objectsWithFetchSpecification on this editing context - * with this editing context as the parameter. - */ - public NSArray objectsWithFetchSpecification ( - EOFetchSpecification aFetchSpec ) - { - return objectsWithFetchSpecification( aFetchSpec, this ); - } - - /** - * Returns a read-only List of objects the meet the criteria of - * the supplied specification. Faults are not allowed in the array. - * If any objects are already fetched, they should not be - * refetched. All objects should belong to the specified editing context. - * This implementation forwards the call to the parent object - * store, which will register each object in the specified editing - * context only if it does not already exist. - */ - public NSArray objectsWithFetchSpecification ( - EOFetchSpecification aFetchSpec, - EOEditingContext aContext) - { - if ( aContext == this ) - { - Object result = notifyDelegate( - "editingContextShouldFetchObjects", - new Class[] { EOEditingContext.class, EOFetchSpecification.class }, - new Object[] { aContext, aFetchSpec } ); - if ( result instanceof NSArray ) return (NSArray) result; - } - return parentStore.objectsWithFetchSpecification( aFetchSpec, aContext ); - } - - /** - * Returns the parent object store for this editing context. - * The result will not be null. - */ - public EOObjectStore parentObjectStore () - { - return parentStore; - } - - /** - * Updates the inserted, updated, and deleted objects lists, - * and posts notifications about which objects have been changed. - * This method is called at the end of an event loop in which - * objects were modified. This method is additionally called - * by saveChanges() so that any changes in the same event loop - * will be processed correctly before calling to the parent - * object store. - * This implementation updates those lists immediately, but - * only posts notifications when this method is called. - */ - public void processRecentChanges () - { // System.out.println( "EOEditingContext.processRecentChanges: " + invalidatedObjectsBuffer ); - - /* - * This implementation actually updates those lists immediately, - * but keeps a separate buffer of changes in the current event - * loop for the purposes of posting a notification. - * NOTE: to reenable buffering, uncomment lines from this method - * body and reenable the RecentChangesObserver in the constructor. - */ - - // broadcast ObjectsChangedInStoreNotification - // for the benefit of child editing contexts - - boolean postStoreInfo = - ( insertedObjectsBuffer.size() + - updatedObjectsBuffer.size() + - deletedIDsBuffer.size() + - invalidatedIDsBuffer.size() > 0 ); - - NSMutableDictionary storeInfo = new NSMutableDictionary(); - if ( postStoreInfo ) - { - storeInfo.setObjectForKey( - globalIDsForObjects( insertedObjectsBuffer ), - // globalIDsForObjects( insertedObjects ), - EOObjectStore.InsertedKey ); - storeInfo.setObjectForKey( - globalIDsForObjects( updatedObjectsBuffer ), - // globalIDsForObjects( updatedObjects ), - EOObjectStore.UpdatedKey ); - storeInfo.setObjectForKey( - new NSArray( (Collection) deletedIDsBuffer ), - // globalIDsForObjects( deletedObjects ), - EOObjectStore.DeletedKey ); - storeInfo.setObjectForKey( - new NSArray( (Collection) invalidatedIDsBuffer ), - EOObjectStore.InvalidatedKey ); - } - - // broadcast ObjectsChangedInEditingContextNotification - // for the benefit of attached display groups - - boolean postContextInfo = - ( insertedObjectsBuffer.size() + - updatedObjectsBuffer.size() + - deletedObjectsBuffer.size() + - invalidatedObjectsBuffer.size() > 0 ); - - NSMutableDictionary contextInfo = new NSMutableDictionary(); - - if ( postContextInfo ) - { - - contextInfo.setObjectForKey( - new NSArray( (Collection) insertedObjectsBuffer ), - // new NSArray( (Collection) insertedObjects ), - EOObjectStore.InsertedKey ); - contextInfo.setObjectForKey( - new NSArray( (Collection) updatedObjectsBuffer ), - // new NSArray( (Collection) updatedObjects ), - EOObjectStore.UpdatedKey ); - contextInfo.setObjectForKey( - new NSArray( (Collection) deletedObjectsBuffer ), - // new NSArray( (Collection) deletedObjects ), - EOObjectStore.DeletedKey ); - contextInfo.setObjectForKey( - new NSArray( (Collection) invalidatedObjectsBuffer ), - EOObjectStore.InvalidatedKey ); - } - - // update the current snapshots - - Object o; - Iterator it; - it = insertedObjectsBuffer.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - registrar.setCurrentSnapshot( o, takeSnapshot( o ) ); - } - it = updatedObjectsBuffer.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - registrar.setCurrentSnapshot( o, takeSnapshot( o ) ); - } - - // clear buffers - - insertedObjectsBuffer.removeAllObjects(); - updatedObjectsBuffer.removeAllObjects(); - deletedObjectsBuffer.removeAllObjects(); - deletedIDsBuffer.removeAllObjects(); - invalidatedObjectsBuffer.removeAllObjects(); - invalidatedIDsBuffer.removeAllObjects(); - - // post notifications (does order matter?) - - if ( postStoreInfo ) - { - NSNotificationCenter.defaultCenter().postNotification( - new NSNotification( - ObjectsChangedInStoreNotification, this, storeInfo ) ); - } - - if ( postContextInfo ) - { - NSNotificationCenter.defaultCenter().postNotification( - new NSNotification( - ObjectsChangedInEditingContextNotification, this, contextInfo ) ); - } - - } - - /** - * Returns whether this editing context propagates deletes - * immediately after the event that triggered the delete. - * Otherwise, propagation occurs only before commit. - */ - public boolean propagatesDeletesAtEndOfEvent () - { - return propagateDeletesAfterEvent; - } - - /** - * Registers the specified object in this editing context - * for the specified id. This method is called by an object - * store when fetching objects for a display group, or when - * objects are inserted into a display group. - * This implementation will re-register the object under the - * new id if it is already registered under a different id. - */ - public void recordObject ( - Object anObject, - EOGlobalID aGlobalID ) - { - // find state for re-registration - boolean inserted = false; - boolean updated = false; - boolean deleted = false; - - // is the object already registered? - EOGlobalID existingID = globalIDForObject( anObject ); - if ( existingID != null ) - { - // remember object state - int index; - index = insertedObjects.indexOfIdenticalObject( anObject ); - if ( index != NSArray.NotFound ) inserted = true; - index = updatedObjects.indexOfIdenticalObject( anObject ); - if ( index != NSArray.NotFound ) updated = true; - index = deletedObjects.indexOfIdenticalObject( anObject ); - if ( index != NSArray.NotFound ) deleted = true; - // forget the object - forgetObject( anObject ); - } - - // is the global id already in use? - Object existingObject = objectForGlobalID( aGlobalID ); - if ( existingObject != null ) - { - // forget it (don't worry about state?) - forgetObject( existingObject ); - } - - registrar.registerObject( anObject, aGlobalID ); - - // restore state if necessary - if ( inserted ) insertedObjects.addObject( anObject ); - if ( updated ) updatedObjects.addObject( anObject ); - if ( deleted ) deletedObjects.addObject( anObject ); - } - - /** - * Undoes the last undo operation. - */ - public void redo () - { - //TODO: not supported yet - throw new UnsupportedOperationException("Not implemented yet."); - } - - /** - * Refaults this editing context, turning all unmodified - * objects into faults. This implementation calls - * editingContextWillSaveChanges() on all editors, and - * then calls refaultObjects(). - */ - public void refault () - { - fireWillSaveChanges(); - refaultObjects(); - } - - /** - * Refaults the specified object, turning it into a fault - * for the specified global id in the specified context. - */ - public void refaultObject ( - Object anObject, - EOGlobalID aGlobalID, - EOEditingContext aContext) - { - aContext.registrar.setCurrentSnapshot( anObject, null ); - - ignoreChanges = true; - parentStore.refaultObject( anObject, aGlobalID, aContext ); - ignoreChanges = false; - + + NSArray result = null; + + if (aContext == this) { + throw new WotonomyException("Assert failed: calling objectsForSourceGlobalID on ourself."); + } + Object source = registrar.objectForGlobalID(aGlobalID); + + // if not registered in our context + if (source == null) { + // get the object registered into our context + result = parentStore.objectsForSourceGlobalID(aGlobalID, aRelationshipKey, this); + } else // source is registered in our context + { + // get existing value + Object value; + if (source instanceof EOKeyValueCoding) { + value = ((EOKeyValueCoding) source).storedValueForKey(aRelationshipKey); + } else // handle directly + { + value = EOKeyValueCodingSupport.storedValueForKey(source, aRelationshipKey); + } + + // if we don't have a valid value on our object + if ((value == null) || ((value instanceof ArrayFault) && (!((ArrayFault) value).isFetched()))) { + // do the same as if the source was null + result = parentStore.objectsForSourceGlobalID(aGlobalID, aRelationshipKey, this); + + // set our value since we have it + if (source instanceof EOKeyValueCoding) { + ((EOKeyValueCoding) source).takeStoredValueForKey(result, aRelationshipKey); + } else // handle directly + { + EOKeyValueCodingSupport.takeStoredValueForKey(source, result, aRelationshipKey); + } + } else if ((value instanceof ArrayFault) && (!((ArrayFault) value).isFetched())) { + // do the same as if the source was null + result = parentStore.objectsForSourceGlobalID(aGlobalID, aRelationshipKey, this); + } else if (value instanceof NSArray) { + result = (NSArray) value; + } else // not NSArray + if (value instanceof Collection) { + // convert to NSArray + result = new NSArray((Collection) value); + } else { + throw new WotonomyException( + "Relationship key did not return a collection: " + aGlobalID + " : " + aRelationshipKey); + } + } + + // if our context is not the specified context + if (aContext != this) { + result = (NSArray) clone(this, result, aContext); + } + + return result; + } + + /** + * Returns a read-only List of objects the meet the criteria of the supplied + * specification. This method simply calls objectsWithFetchSpecification on this + * editing context with this editing context as the parameter. + */ + public NSArray objectsWithFetchSpecification(EOFetchSpecification aFetchSpec) { + return objectsWithFetchSpecification(aFetchSpec, this); + } + + /** + * Returns a read-only List of objects the meet the criteria of the supplied + * specification. Faults are not allowed in the array. If any objects are + * already fetched, they should not be refetched. All objects should belong to + * the specified editing context. This implementation forwards the call to the + * parent object store, which will register each object in the specified editing + * context only if it does not already exist. + */ + public NSArray objectsWithFetchSpecification(EOFetchSpecification aFetchSpec, EOEditingContext aContext) { + if (aContext == this) { + Object result = notifyDelegate("editingContextShouldFetchObjects", + new Class[] { EOEditingContext.class, EOFetchSpecification.class }, + new Object[] { aContext, aFetchSpec }); + if (result instanceof NSArray) + return (NSArray) result; + } + return parentStore.objectsWithFetchSpecification(aFetchSpec, aContext); + } + + /** + * Returns the parent object store for this editing context. The result will not + * be null. + */ + public EOObjectStore parentObjectStore() { + return parentStore; + } + + /** + * Updates the inserted, updated, and deleted objects lists, and posts + * notifications about which objects have been changed. This method is called at + * the end of an event loop in which objects were modified. This method is + * additionally called by saveChanges() so that any changes in the same event + * loop will be processed correctly before calling to the parent object store. + * This implementation updates those lists immediately, but only posts + * notifications when this method is called. + */ + public void processRecentChanges() { // System.out.println( "EOEditingContext.processRecentChanges: " + + // invalidatedObjectsBuffer ); + + /* + * This implementation actually updates those lists immediately, but keeps a + * separate buffer of changes in the current event loop for the purposes of + * posting a notification. NOTE: to reenable buffering, uncomment lines from + * this method body and reenable the RecentChangesObserver in the constructor. + */ + + // broadcast ObjectsChangedInStoreNotification + // for the benefit of child editing contexts + + boolean postStoreInfo = (insertedObjectsBuffer.size() + updatedObjectsBuffer.size() + deletedIDsBuffer.size() + + invalidatedIDsBuffer.size() > 0); + + NSMutableDictionary storeInfo = new NSMutableDictionary(); + if (postStoreInfo) { + storeInfo.setObjectForKey(globalIDsForObjects(insertedObjectsBuffer), + // globalIDsForObjects( insertedObjects ), + EOObjectStore.InsertedKey); + storeInfo.setObjectForKey(globalIDsForObjects(updatedObjectsBuffer), + // globalIDsForObjects( updatedObjects ), + EOObjectStore.UpdatedKey); + storeInfo.setObjectForKey(new NSArray((Collection) deletedIDsBuffer), + // globalIDsForObjects( deletedObjects ), + EOObjectStore.DeletedKey); + storeInfo.setObjectForKey(new NSArray((Collection) invalidatedIDsBuffer), EOObjectStore.InvalidatedKey); + } + + // broadcast ObjectsChangedInEditingContextNotification + // for the benefit of attached display groups + + boolean postContextInfo = (insertedObjectsBuffer.size() + updatedObjectsBuffer.size() + + deletedObjectsBuffer.size() + invalidatedObjectsBuffer.size() > 0); + + NSMutableDictionary contextInfo = new NSMutableDictionary(); + + if (postContextInfo) { + + contextInfo.setObjectForKey(new NSArray((Collection) insertedObjectsBuffer), + // new NSArray( (Collection) insertedObjects ), + EOObjectStore.InsertedKey); + contextInfo.setObjectForKey(new NSArray((Collection) updatedObjectsBuffer), + // new NSArray( (Collection) updatedObjects ), + EOObjectStore.UpdatedKey); + contextInfo.setObjectForKey(new NSArray((Collection) deletedObjectsBuffer), + // new NSArray( (Collection) deletedObjects ), + EOObjectStore.DeletedKey); + contextInfo.setObjectForKey(new NSArray((Collection) invalidatedObjectsBuffer), + EOObjectStore.InvalidatedKey); + } + + // update the current snapshots + + Object o; + Iterator it; + it = insertedObjectsBuffer.iterator(); + while (it.hasNext()) { + o = it.next(); + registrar.setCurrentSnapshot(o, takeSnapshot(o)); + } + it = updatedObjectsBuffer.iterator(); + while (it.hasNext()) { + o = it.next(); + registrar.setCurrentSnapshot(o, takeSnapshot(o)); + } + + // clear buffers + + insertedObjectsBuffer.removeAllObjects(); + updatedObjectsBuffer.removeAllObjects(); + deletedObjectsBuffer.removeAllObjects(); + deletedIDsBuffer.removeAllObjects(); + invalidatedObjectsBuffer.removeAllObjects(); + invalidatedIDsBuffer.removeAllObjects(); + + // post notifications (does order matter?) + + if (postStoreInfo) { + NSNotificationCenter.defaultCenter() + .postNotification(new NSNotification(ObjectsChangedInStoreNotification, this, storeInfo)); + } + + if (postContextInfo) { + NSNotificationCenter.defaultCenter().postNotification( + new NSNotification(ObjectsChangedInEditingContextNotification, this, contextInfo)); + } + + } + + /** + * Returns whether this editing context propagates deletes immediately after the + * event that triggered the delete. Otherwise, propagation occurs only before + * commit. + */ + public boolean propagatesDeletesAtEndOfEvent() { + return propagateDeletesAfterEvent; + } + + /** + * Registers the specified object in this editing context for the specified id. + * This method is called by an object store when fetching objects for a display + * group, or when objects are inserted into a display group. This implementation + * will re-register the object under the new id if it is already registered + * under a different id. + */ + public void recordObject(Object anObject, EOGlobalID aGlobalID) { + // find state for re-registration + boolean inserted = false; + boolean updated = false; + boolean deleted = false; + + // is the object already registered? + EOGlobalID existingID = globalIDForObject(anObject); + if (existingID != null) { + // remember object state + int index; + index = insertedObjects.indexOfIdenticalObject(anObject); + if (index != NSArray.NotFound) + inserted = true; + index = updatedObjects.indexOfIdenticalObject(anObject); + if (index != NSArray.NotFound) + updated = true; + index = deletedObjects.indexOfIdenticalObject(anObject); + if (index != NSArray.NotFound) + deleted = true; + // forget the object + forgetObject(anObject); + } + + // is the global id already in use? + Object existingObject = objectForGlobalID(aGlobalID); + if (existingObject != null) { + // forget it (don't worry about state?) + forgetObject(existingObject); + } + + registrar.registerObject(anObject, aGlobalID); + + // restore state if necessary + if (inserted) + insertedObjects.addObject(anObject); + if (updated) + updatedObjects.addObject(anObject); + if (deleted) + deletedObjects.addObject(anObject); + } + + /** + * Undoes the last undo operation. + */ + public void redo() { + // TODO: not supported yet + throw new UnsupportedOperationException("Not implemented yet."); + } + + /** + * Refaults this editing context, turning all unmodified objects into faults. + * This implementation calls editingContextWillSaveChanges() on all editors, and + * then calls refaultObjects(). + */ + public void refault() { + fireWillSaveChanges(); + refaultObjects(); + } + + /** + * Refaults the specified object, turning it into a fault for the specified + * global id in the specified context. + */ + public void refaultObject(Object anObject, EOGlobalID aGlobalID, EOEditingContext aContext) { + aContext.registrar.setCurrentSnapshot(anObject, null); + + ignoreChanges = true; + parentStore.refaultObject(anObject, aGlobalID, aContext); + ignoreChanges = false; + // remove from updated objects if necessary - int i = updatedObjects.indexOfIdenticalObject( anObject ); - if ( i != NSArray.NotFound ) - { - updatedObjects.removeObjectAtIndex( i ); - } - - // add to invalidated notification queue - invalidatedObjectsBuffer.addObject( anObject ); - invalidatedIDsBuffer.addObject( aGlobalID ); - } - - /** - * Turns all unmodified objects into faults, calling - * processRecentChanges() and then refaultObject() for - * each unmodified object. - */ - public void refaultObjects () - { - // is this call really needed? - // processRecentChanges(); - - Object o; - EOGlobalID id; - Iterator it = registeredObjects().iterator(); - while ( it.hasNext() ) - { - o = it.next(); - if ( ( updatedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) - && ( insertedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) - && ( deletedObjects.indexOfIdenticalObject( o ) == NSArray.NotFound ) ) - { - id = globalIDForObject( o ); - refaultObject( o, id, this ); - } - } - } - - /** - * Calls editingContextWillSaveChanges() on all editors, - * and then calls invalidateAllObjects(). - */ - public void refetch () - { - fireWillSaveChanges(); - invalidateAllObjects(); - } - - /** - * Returns a read-only List of all objects registered in this - * editing context. - */ - public NSArray registeredObjects () - { - return registrar.registeredObjects(); - } - - /** - * Unregisters the specified editor with this editing context. - */ - public void removeEditor ( Object anObject ) - { - if ( anObject == null ) return; - - Object o; - Iterator i = editorSet.iterator(); - while ( i.hasNext() ) - { - o = ((WeakReference)i.next()).get(); - if ( ( o == null ) || ( o == anObject ) ) - { - i.remove(); - } - } - } - - /** - * Unregisters all objects from this editing context, - * and resets the fetch timestamp. - */ - public void reset () - { - Iterator it = registeredObjects().iterator(); - while ( it.hasNext() ) - { - forgetObject( it.next() ); - } - fetchTimestamp = 0; //FIXME: reset timestamp properly - } - - /** - * Reverts the objects in this editing context to - * their original state. - * Calls editingContextWillSaveChanges on all editors, - * discards all inserted objects, restores deleted - * objects, and applies the fetch snapshot to all - * registered objects. - */ - public void revert () - { - willChange(); - fireWillSaveChanges(); - - Iterator it; - - // forget inserted objects - it = new NSArray( insertedObjects ).iterator(); - while ( it.hasNext() ) - { - forgetObject( it.next() ); - } - - EOGlobalID id; - Object o; - byte[] snapshot; - - // re-initialize updated objects - it = new NSArray( updatedObjects ).iterator(); - while ( it.hasNext() ) - { - o = it.next(); - snapshot = (byte[]) registrar.getCommitSnapshot( o ); - if ( snapshot != null ) - { - applySnapshot( snapshot, o ); - } - registrar.setCommitSnapshot( o, null ); - updatedObjectsBuffer.addObject( o ); - } - - // re-initialize deleted objects - it = new NSArray( deletedObjects ).iterator(); - while ( it.hasNext() ) - { - o = it.next(); - snapshot = (byte[]) registrar.getCommitSnapshot( o ); - if ( snapshot != null ) - { - applySnapshot( snapshot, o ); - } - registrar.setCommitSnapshot( o, null ); - updatedObjectsBuffer.addObject( o ); - } - - // reset lists - insertedObjects.removeAllObjects(); // unneccessary? - deletedObjects.removeAllObjects(); - updatedObjects.removeAllObjects(); - - // post notification - processRecentChanges(); - } - - /** - * Returns the root object store, which is the parent - * of all parent object stores of this editing context. - */ - public EOObjectStore rootObjectStore () - { - EOObjectStore parent = parentObjectStore(); - while ( parent instanceof EOEditingContext ) - { - parent = ((EOEditingContext)parent).parentObjectStore(); - } - return parent; - } - - /** - * Calls editingContextWillSaveChanges on all editors, - * and commits all changes in this editing context to - * the parent editing context by calling - * saveChangesInEditingContext to the parent. - * Then posts EditingContextDidSaveChangeNotification. - */ - public void saveChanges () - { + int i = updatedObjects.indexOfIdenticalObject(anObject); + if (i != NSArray.NotFound) { + updatedObjects.removeObjectAtIndex(i); + } + + // add to invalidated notification queue + invalidatedObjectsBuffer.addObject(anObject); + invalidatedIDsBuffer.addObject(aGlobalID); + } + + /** + * Turns all unmodified objects into faults, calling processRecentChanges() and + * then refaultObject() for each unmodified object. + */ + public void refaultObjects() { + // is this call really needed? + // processRecentChanges(); + + Object o; + EOGlobalID id; + Iterator it = registeredObjects().iterator(); + while (it.hasNext()) { + o = it.next(); + if ((updatedObjects.indexOfIdenticalObject(o) == NSArray.NotFound) + && (insertedObjects.indexOfIdenticalObject(o) == NSArray.NotFound) + && (deletedObjects.indexOfIdenticalObject(o) == NSArray.NotFound)) { + id = globalIDForObject(o); + refaultObject(o, id, this); + } + } + } + + /** + * Calls editingContextWillSaveChanges() on all editors, and then calls + * invalidateAllObjects(). + */ + public void refetch() { + fireWillSaveChanges(); + invalidateAllObjects(); + } + + /** + * Returns a read-only List of all objects registered in this editing context. + */ + public NSArray registeredObjects() { + return registrar.registeredObjects(); + } + + /** + * Unregisters the specified editor with this editing context. + */ + public void removeEditor(Object anObject) { + if (anObject == null) + return; + + Object o; + Iterator i = editorSet.iterator(); + while (i.hasNext()) { + o = ((WeakReference) i.next()).get(); + if ((o == null) || (o == anObject)) { + i.remove(); + } + } + } + + /** + * Unregisters all objects from this editing context, and resets the fetch + * timestamp. + */ + public void reset() { + Iterator it = registeredObjects().iterator(); + while (it.hasNext()) { + forgetObject(it.next()); + } + fetchTimestamp = 0; // FIXME: reset timestamp properly + } + + /** + * Reverts the objects in this editing context to their original state. Calls + * editingContextWillSaveChanges on all editors, discards all inserted objects, + * restores deleted objects, and applies the fetch snapshot to all registered + * objects. + */ + public void revert() { + willChange(); + fireWillSaveChanges(); + + Iterator it; + + // forget inserted objects + it = new NSArray(insertedObjects).iterator(); + while (it.hasNext()) { + forgetObject(it.next()); + } + + EOGlobalID id; + Object o; + byte[] snapshot; + + // re-initialize updated objects + it = new NSArray(updatedObjects).iterator(); + while (it.hasNext()) { + o = it.next(); + snapshot = (byte[]) registrar.getCommitSnapshot(o); + if (snapshot != null) { + applySnapshot(snapshot, o); + } + registrar.setCommitSnapshot(o, null); + updatedObjectsBuffer.addObject(o); + } + + // re-initialize deleted objects + it = new NSArray(deletedObjects).iterator(); + while (it.hasNext()) { + o = it.next(); + snapshot = (byte[]) registrar.getCommitSnapshot(o); + if (snapshot != null) { + applySnapshot(snapshot, o); + } + registrar.setCommitSnapshot(o, null); + updatedObjectsBuffer.addObject(o); + } + + // reset lists + insertedObjects.removeAllObjects(); // unneccessary? + deletedObjects.removeAllObjects(); + updatedObjects.removeAllObjects(); + + // post notification + processRecentChanges(); + } + + /** + * Returns the root object store, which is the parent of all parent object + * stores of this editing context. + */ + public EOObjectStore rootObjectStore() { + EOObjectStore parent = parentObjectStore(); + while (parent instanceof EOEditingContext) { + parent = ((EOEditingContext) parent).parentObjectStore(); + } + return parent; + } + + /** + * Calls editingContextWillSaveChanges on all editors, and commits all changes + * in this editing context to the parent editing context by calling + * saveChangesInEditingContext to the parent. Then posts + * EditingContextDidSaveChangeNotification. + */ + public void saveChanges() { //System.out.println( "EOEditingContext.saveChanges: " + this ); - willChange(); - - // process any changes - processRecentChanges(); - - // set up user info for notification to be posted. - NSMutableDictionary userInfo = new NSMutableDictionary(); - userInfo.setObjectForKey( - new NSArray( (Collection) insertedObjects ), - EOObjectStore.InsertedKey ); - userInfo.setObjectForKey( - new NSArray( (Collection) updatedObjects ), - EOObjectStore.UpdatedKey ); - userInfo.setObjectForKey( - new NSArray( (Collection) deletedObjects ), - EOObjectStore.DeletedKey ); - - // notify the editors - fireWillSaveChanges(); - - // notify the delegate - notifyDelegate( - "editingContextWillSaveChanges", - new Class[] { EOEditingContext.class }, - new Object[] { this } ); - - // needed for notification handling - isInvalidating = true; - try - { - // ask parent to save us - parentStore.saveChangesInEditingContext( this ); - } - catch ( RuntimeException e ) - { - // unset save flag and rethrow - isInvalidating = false; - throw e; - } - isInvalidating = false; - - // no exceptions: proceed! - - Object o, key; - Iterator it; - - // update the committed snapshots - it = insertedObjects.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - registrar.setCommitSnapshot( o, null ); - registrar.setCurrentSnapshot( o, null ); - } - it = updatedObjects.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - registrar.setCommitSnapshot( o, null ); - registrar.setCurrentSnapshot( o, null ); - } - - // clear the lists - updatedObjects.removeAllObjects(); - insertedObjects.removeAllObjects(); - it = new NSArray( deletedObjects() ).iterator(); - while ( it.hasNext() ) - { // parent is doing this as well? - forgetObject( it.next() ); - } - - // post notification - NSNotificationCenter.defaultCenter().postNotification( - new NSNotification( - EditingContextDidSaveChangesNotification, this, userInfo ) ); - } - - /** - * Commits all changes in the specified editing context - * to this one. Called by child editing contexts in - * their saveChanges() method. - */ - public void saveChangesInEditingContext ( - EOEditingContext aContext) - { - Object o; - Iterator it; - - // process deletes - List deletes = new NSArray( aContext.deletedObjects() ); - it = deletes.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - EOGlobalID id = aContext.globalIDForObject( o ); - Object localVersion = objectForGlobalID( id ); - if ( localVersion == null ) - { - // make a local copy and register it - localVersion = registerClone( id, aContext, o, this ); - if ( localVersion == null ) - { - throw new WotonomyException( - "Deleted object could not be serialized: " - + id + " : " + o ); - } - } - else // we have a local version, copy changes - { - copy( aContext, o, this, localVersion ); - // copy marks the object as updated: will be on both lists - } - // delete our copy -- marks context as changed - deleteObject( localVersion ); - } - - // process inserts - all inserts are new objects - List inserts = new NSArray( aContext.insertedObjects() ); - it = inserts.iterator(); - while ( it.hasNext() ) - { - o = it.next(); - // make a local copy and register it - EOGlobalID id = aContext.globalIDForObject( o ); - willChange(); // need to mark editing context as changed - Object localVersion = registerClone( id, aContext, o, this ); - if ( localVersion == null ) - { - throw new WotonomyException( - "Inserted object could not be serialized: " - + o ); - } - // insert our copy manually so a new id is not generated - insertedObjects.addObject( localVersion ); - insertedObjectsBuffer.addObject( localVersion ); - } - - // process updates - List updates = new NSArray( aContext.updatedObjects() ); - it = updates.iterator(); - while ( it.hasNext() ) - { - willChange(); // need to mark editing context as changed - o = it.next(); - EOGlobalID id = aContext.globalIDForObject( o ); - Object localVersion = objectForGlobalID( id ); - if ( localVersion == null ) - { - // make a local copy and register it - localVersion = registerClone( id, aContext, o, this ); - if ( localVersion == null ) - { - throw new WotonomyException( - "Updated object could not be serialized: " - + id + " : " + o ); - } - if ( id.isTemporary() ) - { - // mark this object as inserted - insertedObjects.addObject( localVersion ); - insertedObjectsBuffer.addObject( localVersion ); - } - else - { - // mark this object as updated - updatedObjects.addObject( localVersion ); - - // notify of update only if not on deleted list - if ( deletedObjectsBuffer.indexOfIdenticalObject( - localVersion ) == NSArray.NotFound ) - { - updatedObjectsBuffer.addObject( localVersion ); - } - } - } - else // we have a local version, copy changes - { - copy( aContext, o, this, localVersion ); - // copy marks the object as updated - } - } - - } - - /** - * Sets the delegate for this editing context. - * Note: this implementation retains only a - * weak reference to the specified object. - */ - public void setDelegate ( - Object anObject ) - { - if ( anObject == null ) delegate = null; - delegate = new WeakReference( anObject ); - } - - /** - * Sets the fetch timestamp for this editing context. - */ - public void setFetchTimestamp ( - double aDouble ) - { - fetchTimestamp = aDouble; - } -/* - public void setInvalidatesObjectsWhenFinalized ( - boolean invalidatesObjects ) - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } - - public void setInvalidatesObjectsWhenFreed ( - boolean invalidatesObjects ) - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ - /** - * Sets whether this editing context attempts to - * lock objects when they are first modified. - * Default is false. - */ - public void setLocksObjectsBeforeFirstModification ( - boolean locksObjects ) - { - lockBeforeModify = locksObjects; - } - - /** - * Sets the message handler for this editing context. - * Note: this implementation retains only a - * weak reference to the specified object. - */ - public void setMessageHandler ( - Object anObject ) - { - if ( anObject == null ) messageHandler = null; - messageHandler = new WeakReference( anObject ); - } - - /** - * Sets whether this editing context propagates deletes - * immediately after the event that triggered the delete. - * Otherwise, propagation occurs only before commit. - * Default is true. - */ - public void setPropagatesDeletesAtEndOfEvent ( - boolean propagatesDeletes ) - { - propagateDeletesAfterEvent = propagatesDeletes; - } -/* - public void setSharedEditingContext ( - EOSharedEditingContext aSharedEditingContext ) - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ - /** - * Sets whether validation is stopped after the - * first error occurs. Otherwise, validation will - * continue for all other objects. - * Default is true. - */ - public void setStopsValidationAfterFirstError ( - boolean stopsValidation ) - { - stopValidationAfterError = stopsValidation; - } - - /** - * Sets the undo manager to be used for this context. - * Note: This is currently javax.swing.undo.UndoManager, - * until we have a implementation of NSUndoManager. - */ -/* - public void setUndoManager ( - UndoManager anUndoManager ) - { - undoManager = anUndoManager; - } -*/ -/* - public EOSharedEditingContext sharedEditingContext () - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ - /** - * Returns whether validation is stopped after the - * first error occurs. Otherwise, validation will - * continue for all other objects. - */ - public boolean stopsValidationAfterFirstError () - { - return stopValidationAfterError; - } - - /** - * Reverts the last change on the undo stack. - */ - public void undo () - { - //TODO: not supported yet - throw new UnsupportedOperationException("Not implemented yet."); - } -/* - public NSUndoManager undoManager () - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ -/** - public void unlock () - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ - /** - * Returns a read-only list of all objects marked as modified, - * but not inserted or deleted, in this editing context. - */ - public NSArray updatedObjects () - { - return updatedObjectsProxy; - } - - /** - * Notify editors of changes. - */ - private void fireWillSaveChanges() - { - Object o = null; - Iterator i = editors().iterator(); - while ( i.hasNext() ) - { - try - { - o = i.next(); - NSSelector.invoke( "editingContextWillSaveChanges", - new Class[] { EOEditingContext.class }, o, this ); - } - catch ( NoSuchMethodException e ) - { - // ignore: not implemented - } - catch ( Exception exc ) - { - // log to standard error - System.err.println( "Error while notifying editor of pending save: " + o ); - exc.printStackTrace(); - } - } - } - - /** - * Handles notifications from parent store, looking for - * InvalidatedAllObjectsInStoreNotification and - * ObjectsChangedInStoreNotification. - * The former causes all objects in this store to be - * invalidated. - * The latter refaults the invalidated ids, merges changes - * from the updated ids, and forgets the deleted ids, then - * posts a ObjectsChangedInStoreNotification. - * Note: This method is not in the public specification. - */ - public void handleNotification( NSNotification aNotification ) - { // System.out.println( "EOEditingContext: " + this + " : " + aNotification ); - - willChange(); - if ( InvalidatedAllObjectsInStoreNotification - .equals( aNotification.name() ) ) - { - refaultObjects(); - - // relay notification - NSNotificationCenter.defaultCenter().postNotification( - new NSNotification( - InvalidatedAllObjectsInStoreNotification, this ) ); - } - else - if ( EOGlobalID.GlobalIDChangedNotification - .equals( aNotification.name() ) ) - { - NSDictionary userInfo = aNotification.userInfo(); - - // if any keys in userInfo are registered ids, - // re-register with new permanent values. - - Object o; - EOGlobalID id; - Enumeration e = userInfo.keyEnumerator(); - while ( e.hasMoreElements() ) - { - id = (EOGlobalID) e.nextElement(); - o = objectForGlobalID( id ); - if ( o != null ) - { - // record object is assumed to handle key updates - recordObject( o, (EOGlobalID) userInfo.objectForKey( id ) ); - } - } - } - else - if ( EOObjectStore.ObjectsChangedInStoreNotification - .equals( aNotification.name() ) ) - { - //System.out.println( "EOEditingContext.handleNotification: " + aNotification + " : " + this ); - // post so child contexts are notified - willChange(); - - Object o; - EOGlobalID id; - Enumeration e; - NSDictionary userInfo = aNotification.userInfo(); - - // inserted objects are ignored - - // existing deleted objects are removed - NSArray deletes = (NSArray) userInfo.objectForKey( - EOObjectStore.DeletedKey ); - e = deletes.objectEnumerator(); - while ( e.hasMoreElements() ) - { - id = (EOGlobalID) e.nextElement(); - o = objectForGlobalID( id ); - if ( o != null ) - { - //System.out.println( "EOEditingContext: deleted: " + id ); - forgetObject( o ); - deletedObjectsBuffer.addObject( o ); - deletedIDsBuffer.addObject( id ); - } - } - - // existing updated objects are merged - NSArray updates = (NSArray) userInfo.objectForKey( - EOObjectStore.UpdatedKey ); - e = updates.objectEnumerator(); - while ( e.hasMoreElements() ) - { - id = (EOGlobalID) e.nextElement(); - o = objectForGlobalID( id ); - if ( o != null ) - { - //System.out.println( "EOEditingContext: updated: " + id ); - if ( updatedObjects // only update if unchanged - .indexOfIdenticalObject( o ) == NSArray.NotFound ) - { - refaultObject( o, id, this ); - updatedObjectsBuffer.addObject( o ); - } - else - { - // notify user and/or merge - handleUpdateConflict( id, o ); - } - } - } - - // existing invalidated objects are refaulted - NSArray invalidates = (NSArray) userInfo.objectForKey( - EOObjectStore.InvalidatedKey ); - e = invalidates.objectEnumerator(); - while ( e.hasMoreElements() ) - { - id = (EOGlobalID) e.nextElement(); - o = objectForGlobalID( id ); - if ( o != null ) - { - if ( updatedObjects // only invalidate if unchanged - .indexOfIdenticalObject( o ) == NSArray.NotFound ) - { - refaultObject( o, id, this ); - } - else - { - // notify user and/or merge - handleUpdateConflict( id, o ); - } - if ( invalidatedObjectsBuffer - .indexOfIdenticalObject( o ) == NSArray.NotFound ) - { - invalidatedObjectsBuffer.addObject( o ); - } - if ( invalidatedIDsBuffer - .indexOfIdenticalObject( id ) == NSArray.NotFound ) - { - invalidatedIDsBuffer.addObject( id ); - } - } - } - - } - } - - /** - * Called by handleNotification to resolve the case where we have - * received notification that another user or context has updated - * an object that is currently marked as edited in this context. - * This implementation first asks the delegate if it should merge. - * If true or no delegate, the changes are merged. True or false, - * the delegate is then sent editingContextDidMergeChanges. - */ - private void handleUpdateConflict( EOGlobalID anID, Object anObject ) - { - // if we're causing the invalidation, ignore - // (this is probably better handled by the caller...) - if ( isInvalidating ) - { - ignoreChanges = true; - parentStore.refaultObject( anObject, anID, this ); - ignoreChanges = false; - return; - } - - Boolean result = (Boolean) notifyDelegate( - "editingContextShouldMergeChangesForObject", - new Class[] { EOEditingContext.class, Object.class }, - new Object[] { this, anObject } ); - - if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) ) - { - // do merge - mergeExternalChanges( anID, anObject ); - } - else // Boolean.FALSE - { - // do nothing: don't lose the user's changes - } - - // notify merge did happen - notifyDelegate( - "editingContextDidMergeChanges", - new Class[] { EOEditingContext.class }, - new Object[] { this } ); - } - - /** - * For the currently modified object with the specified global id, - * this method merges changes with the updated version in the parent - * object store. This implementation looks at the fetch snapshot - * to determine which changes where made by the user, fetches the - * updated version of the object, and then determine what external - * changes were made. If the changes do not overlap, the original - * changes are applied to the updated version. If there is a conflict, - * notifies the user of the conflict. - */ - private boolean mergeExternalChanges( EOGlobalID anID, Object anObject ) - { - try - { - Iterator i; - Object key; - - // get fetch snapshot - Map fetchSnapshot = committedSnapshotForObject( anObject ); - - // get current snapshot - Map currentSnapshot = currentEventSnapshotForObject( anObject ); - - // diff against fetch snapshot - Map currentDiff = new HashMap(); - i = currentSnapshot.keySet().iterator(); - while ( i.hasNext() ) - { - key = i.next(); - if ( ! currentSnapshot.get( key ).equals( fetchSnapshot.get( key ) ) ) - { - currentDiff.put( key, currentSnapshot.get( key ) ); - } - } - - // refault - ignoreChanges = true; - parentStore.refaultObject( anObject, anID, this ); - ignoreChanges = false; - - // get updated snapshot - Map updatedSnapshot = convertSnapshotToDictionary( takeSnapshot( anObject ) ); - - // diff against fetch snapshot - Map updatedDiff = new HashMap(); - i = updatedSnapshot.keySet().iterator(); - while ( i.hasNext() ) - { - key = i.next(); - if ( ! updatedSnapshot.get( key ).equals( fetchSnapshot.get( key ) ) ) - { - updatedDiff.put( key, updatedSnapshot.get( key ) ); - } - } - - // determine if there's a conflict - boolean proceed = true; - Set updatedKeys = updatedDiff.keySet(); - i = currentDiff.keySet().iterator(); - while ( i.hasNext() ) - { - if ( updatedKeys.contains( i.next() ) ) - { - proceed = false; - break; - } - } - - // if no conflicts, apply original diff to current object and exit - if ( proceed ) - { - KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, currentDiff ); - return true; // exit! - } - } - catch ( Exception exc ) - { - // log error to standard out - exc.printStackTrace(); - } - - // notify user we're unable to merge - notifyMessageHandler( MessageChangeConflict + anObject ); - return false; - } - - /** - * Sends the specified message to the message handler. - */ - private void notifyMessageHandler( String aMessage ) - { - Object handler = null; - try - { - handler = messageHandler(); - if ( handler == null ) return; - NSSelector.invoke( "editingContextPresentErrorMessage", - new Class[] { EOEditingContext.class, String.class }, - handler, this, aMessage ); - } - catch ( NoSuchMethodException e ) - { - // ignore: not implemented - } - catch ( Exception exc ) - { - // log to standard error - System.err.println( - "Error while notifying message handler: " + - handler + " : " + aMessage ); - exc.printStackTrace(); - } - } - - /** - * Sends the specified message to the delegate. - * Returns the return value of the method, - * or null if no return value or no delegate - * or no implementation. - */ - private Object notifyDelegate( - String aMethodName, Class[] types, Object[] params ) - { - try - { - Object delegate = delegate(); - if ( delegate == null ) return null; - return NSSelector.invoke( - aMethodName, types, delegate, params ); - } - catch ( NoSuchMethodException e ) - { - // ignore: not implemented - } - catch ( Exception exc ) - { - // log to standard error - System.err.println( - "Error while messaging delegate: " + - delegate + " : " + aMethodName ); - exc.printStackTrace(); - } - - return null; - } - - // interface EOObserving - - /** - * Implementation of the EOObserving interface. - * Called before objects are modified. - */ - public void objectWillChange ( - Object anObject ) - { - if ( ignoreChanges ) return; + willChange(); + + // process any changes + processRecentChanges(); + + // set up user info for notification to be posted. + NSMutableDictionary userInfo = new NSMutableDictionary(); + userInfo.setObjectForKey(new NSArray((Collection) insertedObjects), EOObjectStore.InsertedKey); + userInfo.setObjectForKey(new NSArray((Collection) updatedObjects), EOObjectStore.UpdatedKey); + userInfo.setObjectForKey(new NSArray((Collection) deletedObjects), EOObjectStore.DeletedKey); + + // notify the editors + fireWillSaveChanges(); + + // notify the delegate + notifyDelegate("editingContextWillSaveChanges", new Class[] { EOEditingContext.class }, new Object[] { this }); + + // needed for notification handling + isInvalidating = true; + try { + // ask parent to save us + parentStore.saveChangesInEditingContext(this); + } catch (RuntimeException e) { + // unset save flag and rethrow + isInvalidating = false; + throw e; + } + isInvalidating = false; + + // no exceptions: proceed! + + Object o, key; + Iterator it; + + // update the committed snapshots + it = insertedObjects.iterator(); + while (it.hasNext()) { + o = it.next(); + registrar.setCommitSnapshot(o, null); + registrar.setCurrentSnapshot(o, null); + } + it = updatedObjects.iterator(); + while (it.hasNext()) { + o = it.next(); + registrar.setCommitSnapshot(o, null); + registrar.setCurrentSnapshot(o, null); + } + + // clear the lists + updatedObjects.removeAllObjects(); + insertedObjects.removeAllObjects(); + it = new NSArray(deletedObjects()).iterator(); + while (it.hasNext()) { // parent is doing this as well? + forgetObject(it.next()); + } + + // post notification + NSNotificationCenter.defaultCenter() + .postNotification(new NSNotification(EditingContextDidSaveChangesNotification, this, userInfo)); + } + + /** + * Commits all changes in the specified editing context to this one. Called by + * child editing contexts in their saveChanges() method. + */ + public void saveChangesInEditingContext(EOEditingContext aContext) { + Object o; + Iterator it; + + // process deletes + List deletes = new NSArray(aContext.deletedObjects()); + it = deletes.iterator(); + while (it.hasNext()) { + o = it.next(); + EOGlobalID id = aContext.globalIDForObject(o); + Object localVersion = objectForGlobalID(id); + if (localVersion == null) { + // make a local copy and register it + localVersion = registerClone(id, aContext, o, this); + if (localVersion == null) { + throw new WotonomyException("Deleted object could not be serialized: " + id + " : " + o); + } + } else // we have a local version, copy changes + { + copy(aContext, o, this, localVersion); + // copy marks the object as updated: will be on both lists + } + // delete our copy -- marks context as changed + deleteObject(localVersion); + } + + // process inserts - all inserts are new objects + List inserts = new NSArray(aContext.insertedObjects()); + it = inserts.iterator(); + while (it.hasNext()) { + o = it.next(); + // make a local copy and register it + EOGlobalID id = aContext.globalIDForObject(o); + willChange(); // need to mark editing context as changed + Object localVersion = registerClone(id, aContext, o, this); + if (localVersion == null) { + throw new WotonomyException("Inserted object could not be serialized: " + o); + } + // insert our copy manually so a new id is not generated + insertedObjects.addObject(localVersion); + insertedObjectsBuffer.addObject(localVersion); + } + + // process updates + List updates = new NSArray(aContext.updatedObjects()); + it = updates.iterator(); + while (it.hasNext()) { + willChange(); // need to mark editing context as changed + o = it.next(); + EOGlobalID id = aContext.globalIDForObject(o); + Object localVersion = objectForGlobalID(id); + if (localVersion == null) { + // make a local copy and register it + localVersion = registerClone(id, aContext, o, this); + if (localVersion == null) { + throw new WotonomyException("Updated object could not be serialized: " + id + " : " + o); + } + if (id.isTemporary()) { + // mark this object as inserted + insertedObjects.addObject(localVersion); + insertedObjectsBuffer.addObject(localVersion); + } else { + // mark this object as updated + updatedObjects.addObject(localVersion); + + // notify of update only if not on deleted list + if (deletedObjectsBuffer.indexOfIdenticalObject(localVersion) == NSArray.NotFound) { + updatedObjectsBuffer.addObject(localVersion); + } + } + } else // we have a local version, copy changes + { + copy(aContext, o, this, localVersion); + // copy marks the object as updated + } + } + + } + + /** + * Sets the delegate for this editing context. Note: this implementation retains + * only a weak reference to the specified object. + */ + public void setDelegate(Object anObject) { + if (anObject == null) + delegate = null; + delegate = new WeakReference(anObject); + } + + /** + * Sets the fetch timestamp for this editing context. + */ + public void setFetchTimestamp(double aDouble) { + fetchTimestamp = aDouble; + } + + /* + * public void setInvalidatesObjectsWhenFinalized ( boolean invalidatesObjects ) + * { throw new net.wotonomy.util.WotonomyException("Not implemented yet."); } + * + * public void setInvalidatesObjectsWhenFreed ( boolean invalidatesObjects ) { + * throw new net.wotonomy.util.WotonomyException("Not implemented yet."); } + */ + /** + * Sets whether this editing context attempts to lock objects when they are + * first modified. Default is false. + */ + public void setLocksObjectsBeforeFirstModification(boolean locksObjects) { + lockBeforeModify = locksObjects; + } + + /** + * Sets the message handler for this editing context. Note: this implementation + * retains only a weak reference to the specified object. + */ + public void setMessageHandler(Object anObject) { + if (anObject == null) + messageHandler = null; + messageHandler = new WeakReference(anObject); + } + + /** + * Sets whether this editing context propagates deletes immediately after the + * event that triggered the delete. Otherwise, propagation occurs only before + * commit. Default is true. + */ + public void setPropagatesDeletesAtEndOfEvent(boolean propagatesDeletes) { + propagateDeletesAfterEvent = propagatesDeletes; + } + + /* + * public void setSharedEditingContext ( EOSharedEditingContext + * aSharedEditingContext ) { throw new + * net.wotonomy.util.WotonomyException("Not implemented yet."); } + */ + /** + * Sets whether validation is stopped after the first error occurs. Otherwise, + * validation will continue for all other objects. Default is true. + */ + public void setStopsValidationAfterFirstError(boolean stopsValidation) { + stopValidationAfterError = stopsValidation; + } + + /** + * Sets the undo manager to be used for this context. Note: This is currently + * javax.swing.undo.UndoManager, until we have a implementation of + * NSUndoManager. + */ + /* + * public void setUndoManager ( UndoManager anUndoManager ) { undoManager = + * anUndoManager; } + */ + /* + * public EOSharedEditingContext sharedEditingContext () { throw new + * net.wotonomy.util.WotonomyException("Not implemented yet."); } + */ + /** + * Returns whether validation is stopped after the first error occurs. + * Otherwise, validation will continue for all other objects. + */ + public boolean stopsValidationAfterFirstError() { + return stopValidationAfterError; + } + + /** + * Reverts the last change on the undo stack. + */ + public void undo() { + // TODO: not supported yet + throw new UnsupportedOperationException("Not implemented yet."); + } + + /* + * public NSUndoManager undoManager () { throw new + * net.wotonomy.util.WotonomyException("Not implemented yet."); } + */ + /** + * public void unlock () { throw new net.wotonomy.util.WotonomyException("Not + * implemented yet."); } + */ + /** + * Returns a read-only list of all objects marked as modified, but not inserted + * or deleted, in this editing context. + */ + public NSArray updatedObjects() { + return updatedObjectsProxy; + } + + /** + * Notify editors of changes. + */ + private void fireWillSaveChanges() { + Object o = null; + Iterator i = editors().iterator(); + while (i.hasNext()) { + try { + o = i.next(); + NSSelector.invoke("editingContextWillSaveChanges", new Class[] { EOEditingContext.class }, o, this); + } catch (NoSuchMethodException e) { + // ignore: not implemented + } catch (Exception exc) { + // log to standard error + System.err.println("Error while notifying editor of pending save: " + o); + exc.printStackTrace(); + } + } + } + + /** + * Handles notifications from parent store, looking for + * InvalidatedAllObjectsInStoreNotification and + * ObjectsChangedInStoreNotification. The former causes all objects in this + * store to be invalidated. The latter refaults the invalidated ids, merges + * changes from the updated ids, and forgets the deleted ids, then posts a + * ObjectsChangedInStoreNotification. Note: This method is not in the public + * specification. + */ + public void handleNotification(NSNotification aNotification) { // System.out.println( "EOEditingContext: " + this + + // " : " + aNotification ); + + willChange(); + if (InvalidatedAllObjectsInStoreNotification.equals(aNotification.name())) { + refaultObjects(); + + // relay notification + NSNotificationCenter.defaultCenter() + .postNotification(new NSNotification(InvalidatedAllObjectsInStoreNotification, this)); + } else if (EOGlobalID.GlobalIDChangedNotification.equals(aNotification.name())) { + NSDictionary userInfo = aNotification.userInfo(); + + // if any keys in userInfo are registered ids, + // re-register with new permanent values. + + Object o; + EOGlobalID id; + Enumeration e = userInfo.keyEnumerator(); + while (e.hasMoreElements()) { + id = (EOGlobalID) e.nextElement(); + o = objectForGlobalID(id); + if (o != null) { + // record object is assumed to handle key updates + recordObject(o, (EOGlobalID) userInfo.objectForKey(id)); + } + } + } else if (EOObjectStore.ObjectsChangedInStoreNotification.equals(aNotification.name())) { + // System.out.println( "EOEditingContext.handleNotification: " + aNotification + + // " : " + this ); + // post so child contexts are notified + willChange(); + + Object o; + EOGlobalID id; + Enumeration e; + NSDictionary userInfo = aNotification.userInfo(); + + // inserted objects are ignored + + // existing deleted objects are removed + NSArray deletes = (NSArray) userInfo.objectForKey(EOObjectStore.DeletedKey); + e = deletes.objectEnumerator(); + while (e.hasMoreElements()) { + id = (EOGlobalID) e.nextElement(); + o = objectForGlobalID(id); + if (o != null) { + // System.out.println( "EOEditingContext: deleted: " + id ); + forgetObject(o); + deletedObjectsBuffer.addObject(o); + deletedIDsBuffer.addObject(id); + } + } + + // existing updated objects are merged + NSArray updates = (NSArray) userInfo.objectForKey(EOObjectStore.UpdatedKey); + e = updates.objectEnumerator(); + while (e.hasMoreElements()) { + id = (EOGlobalID) e.nextElement(); + o = objectForGlobalID(id); + if (o != null) { + // System.out.println( "EOEditingContext: updated: " + id ); + if (updatedObjects // only update if unchanged + .indexOfIdenticalObject(o) == NSArray.NotFound) { + refaultObject(o, id, this); + updatedObjectsBuffer.addObject(o); + } else { + // notify user and/or merge + handleUpdateConflict(id, o); + } + } + } + + // existing invalidated objects are refaulted + NSArray invalidates = (NSArray) userInfo.objectForKey(EOObjectStore.InvalidatedKey); + e = invalidates.objectEnumerator(); + while (e.hasMoreElements()) { + id = (EOGlobalID) e.nextElement(); + o = objectForGlobalID(id); + if (o != null) { + if (updatedObjects // only invalidate if unchanged + .indexOfIdenticalObject(o) == NSArray.NotFound) { + refaultObject(o, id, this); + } else { + // notify user and/or merge + handleUpdateConflict(id, o); + } + if (invalidatedObjectsBuffer.indexOfIdenticalObject(o) == NSArray.NotFound) { + invalidatedObjectsBuffer.addObject(o); + } + if (invalidatedIDsBuffer.indexOfIdenticalObject(id) == NSArray.NotFound) { + invalidatedIDsBuffer.addObject(id); + } + } + } + + } + } + + /** + * Called by handleNotification to resolve the case where we have received + * notification that another user or context has updated an object that is + * currently marked as edited in this context. This implementation first asks + * the delegate if it should merge. If true or no delegate, the changes are + * merged. True or false, the delegate is then sent + * editingContextDidMergeChanges. + */ + private void handleUpdateConflict(EOGlobalID anID, Object anObject) { + // if we're causing the invalidation, ignore + // (this is probably better handled by the caller...) + if (isInvalidating) { + ignoreChanges = true; + parentStore.refaultObject(anObject, anID, this); + ignoreChanges = false; + return; + } + + Boolean result = (Boolean) notifyDelegate("editingContextShouldMergeChangesForObject", + new Class[] { EOEditingContext.class, Object.class }, new Object[] { this, anObject }); + + if ((result == null) || (Boolean.TRUE.equals(result))) { + // do merge + mergeExternalChanges(anID, anObject); + } else // Boolean.FALSE + { + // do nothing: don't lose the user's changes + } + + // notify merge did happen + notifyDelegate("editingContextDidMergeChanges", new Class[] { EOEditingContext.class }, new Object[] { this }); + } + + /** + * For the currently modified object with the specified global id, this method + * merges changes with the updated version in the parent object store. This + * implementation looks at the fetch snapshot to determine which changes where + * made by the user, fetches the updated version of the object, and then + * determine what external changes were made. If the changes do not overlap, the + * original changes are applied to the updated version. If there is a conflict, + * notifies the user of the conflict. + */ + private boolean mergeExternalChanges(EOGlobalID anID, Object anObject) { + try { + Iterator i; + Object key; + + // get fetch snapshot + Map fetchSnapshot = committedSnapshotForObject(anObject); + + // get current snapshot + Map currentSnapshot = currentEventSnapshotForObject(anObject); + + // diff against fetch snapshot + Map currentDiff = new HashMap(); + i = currentSnapshot.keySet().iterator(); + while (i.hasNext()) { + key = i.next(); + if (!currentSnapshot.get(key).equals(fetchSnapshot.get(key))) { + currentDiff.put(key, currentSnapshot.get(key)); + } + } + + // refault + ignoreChanges = true; + parentStore.refaultObject(anObject, anID, this); + ignoreChanges = false; + + // get updated snapshot + Map updatedSnapshot = convertSnapshotToDictionary(takeSnapshot(anObject)); + + // diff against fetch snapshot + Map updatedDiff = new HashMap(); + i = updatedSnapshot.keySet().iterator(); + while (i.hasNext()) { + key = i.next(); + if (!updatedSnapshot.get(key).equals(fetchSnapshot.get(key))) { + updatedDiff.put(key, updatedSnapshot.get(key)); + } + } + + // determine if there's a conflict + boolean proceed = true; + Set updatedKeys = updatedDiff.keySet(); + i = currentDiff.keySet().iterator(); + while (i.hasNext()) { + if (updatedKeys.contains(i.next())) { + proceed = false; + break; + } + } + + // if no conflicts, apply original diff to current object and exit + if (proceed) { + KeyValueCodingUtilities.takeStoredValuesFromDictionary(anObject, currentDiff); + return true; // exit! + } + } catch (Exception exc) { + // log error to standard out + exc.printStackTrace(); + } + + // notify user we're unable to merge + notifyMessageHandler(MessageChangeConflict + anObject); + return false; + } + + /** + * Sends the specified message to the message handler. + */ + private void notifyMessageHandler(String aMessage) { + Object handler = null; + try { + handler = messageHandler(); + if (handler == null) + return; + NSSelector.invoke("editingContextPresentErrorMessage", new Class[] { EOEditingContext.class, String.class }, + handler, this, aMessage); + } catch (NoSuchMethodException e) { + // ignore: not implemented + } catch (Exception exc) { + // log to standard error + System.err.println("Error while notifying message handler: " + handler + " : " + aMessage); + exc.printStackTrace(); + } + } + + /** + * Sends the specified message to the delegate. Returns the return value of the + * method, or null if no return value or no delegate or no implementation. + */ + private Object notifyDelegate(String aMethodName, Class[] types, Object[] params) { + try { + Object delegate = delegate(); + if (delegate == null) + return null; + return NSSelector.invoke(aMethodName, types, delegate, params); + } catch (NoSuchMethodException e) { + // ignore: not implemented + } catch (Exception exc) { + // log to standard error + System.err.println("Error while messaging delegate: " + delegate + " : " + aMethodName); + exc.printStackTrace(); + } + + return null; + } + + // interface EOObserving + + /** + * Implementation of the EOObserving interface. Called before objects are + * modified. + */ + public void objectWillChange(Object anObject) { + if (ignoreChanges) + return; //NSNotificationCenter.defaultCenter().postNotification( "objectWillChange", this, new NSDictionary( "object", anObject ) ); //new RuntimeException().printStackTrace(); - willChange(); - - // mark as updated if not marked already - int i = updatedObjects.indexOfIdenticalObject( anObject ); - if ( i == NSArray.NotFound ) - { - // don't mark inserted objects as updated - i = insertedObjects.indexOfIdenticalObject( anObject ); - if ( i == NSArray.NotFound ) - { - i = deletedObjects.indexOfIdenticalObject( anObject ); - if ( i == NSArray.NotFound ) - { - // add object - updatedObjects.addObject( anObject ); - - // record revert snapshot - registrar.setCommitSnapshot( anObject, takeSnapshot( anObject ) ); - } - } - } - - // add to buffer - if ( updatedObjectsBuffer.indexOfIdenticalObject( anObject ) - == NSArray.NotFound ) - { - updatedObjectsBuffer.addObject( anObject ); - } - } - - // static methods - - public static double defaultFetchTimestampLag () - { - return defaultFetchTimestampLag; - } - - /** - * Returns the default parent object store for all - * object stores created with the parameterless - * constructor. - */ - public static EOObjectStore defaultParentObjectStore () - { - return defaultParentObjectStore; - } + willChange(); + + // mark as updated if not marked already + int i = updatedObjects.indexOfIdenticalObject(anObject); + if (i == NSArray.NotFound) { + // don't mark inserted objects as updated + i = insertedObjects.indexOfIdenticalObject(anObject); + if (i == NSArray.NotFound) { + i = deletedObjects.indexOfIdenticalObject(anObject); + if (i == NSArray.NotFound) { + // add object + updatedObjects.addObject(anObject); + + // record revert snapshot + registrar.setCommitSnapshot(anObject, takeSnapshot(anObject)); + } + } + } -/* - public static Object initObjectWithCoder ( - Object anObject, - NSCoder aCoder ) - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ + // add to buffer + if (updatedObjectsBuffer.indexOfIdenticalObject(anObject) == NSArray.NotFound) { + updatedObjectsBuffer.addObject(anObject); + } + } - /** - * Returns whether editing contexts are configured to retain strong - * references to their registered objects. If false, editing contexts - * will only retain weak references to their registered objects. - */ - public static boolean instancesRetainRegisteredObjects() - { - return retainsRegisteredObjects; - } - - /** - * Sets the global default fetch timestamp lag. - */ - public static void setDefaultFetchTimestampLag ( - double aDouble ) - { - defaultFetchTimestampLag = aDouble; - } - - /** - * Sets the global default parent object store, - * used for the parameterless constructor. - */ - public static void setDefaultParentObjectStore ( - EOObjectStore anObjectStore ) - { - defaultParentObjectStore = anObjectStore; - } - - public static void setInstancesRetainRegisteredObjects ( - boolean retainsObjects ) - { - retainsRegisteredObjects = retainsObjects; - } + // static methods -/* - public static void setSubstitutionEditingContext ( - EOEditingContext aContext) - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } - - public static void setUsesContextRelativeEncoding ( - boolean usesRelativeEncoding ) - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } - - public static EOEditingContext substitutionEditingContext () - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } - - public static boolean usesContextRelativeEncoding () - { - throw new net.wotonomy.util.WotonomyException("Not implemented yet."); - } -*/ + public static double defaultFetchTimestampLag() { + return defaultFetchTimestampLag; + } + + /** + * Returns the default parent object store for all object stores created with + * the parameterless constructor. + */ + public static EOObjectStore defaultParentObjectStore() { + return defaultParentObjectStore; + } + + /* + * public static Object initObjectWithCoder ( Object anObject, NSCoder aCoder ) + * { throw new net.wotonomy.util.WotonomyException("Not implemented yet."); } + */ + + /** + * Returns whether editing contexts are configured to retain strong references + * to their registered objects. If false, editing contexts will only retain weak + * references to their registered objects. + */ + public static boolean instancesRetainRegisteredObjects() { + return retainsRegisteredObjects; + } + + /** + * Sets the global default fetch timestamp lag. + */ + public static void setDefaultFetchTimestampLag(double aDouble) { + defaultFetchTimestampLag = aDouble; + } + + /** + * Sets the global default parent object store, used for the parameterless + * constructor. + */ + public static void setDefaultParentObjectStore(EOObjectStore anObjectStore) { + defaultParentObjectStore = anObjectStore; + } + + public static void setInstancesRetainRegisteredObjects(boolean retainsObjects) { + retainsRegisteredObjects = retainsObjects; + } + + /* + * public static void setSubstitutionEditingContext ( EOEditingContext aContext) + * { throw new net.wotonomy.util.WotonomyException("Not implemented yet."); } + * + * public static void setUsesContextRelativeEncoding ( boolean + * usesRelativeEncoding ) { throw new + * net.wotonomy.util.WotonomyException("Not implemented yet."); } + * + * public static EOEditingContext substitutionEditingContext () { throw new + * net.wotonomy.util.WotonomyException("Not implemented yet."); } + * + * public static boolean usesContextRelativeEncoding () { throw new + * net.wotonomy.util.WotonomyException("Not implemented yet."); } + */ + + public String toString() { + return "[EOEditingContext@" + Integer.toHexString(System.identityHashCode(this)) + ":" + " inserted=" + + idsForObjects(insertedObjects) + " updated=" + idsForObjects(updatedObjects) + " deleted=" + + idsForObjects(deletedObjects) + " registered=" + registrar.registeredGlobalIDs() + " ]"; + } + + private List idsForObjects(List objects) { + List result = new LinkedList(); + Iterator i = objects.iterator(); + while (i.hasNext()) + result.add(globalIDForObject(i.next())); + return result; + } - public String toString() - { - return "[EOEditingContext@"+Integer.toHexString(System.identityHashCode(this))+":"+ - " inserted="+idsForObjects(insertedObjects)+ - " updated="+idsForObjects(updatedObjects)+ - " deleted="+idsForObjects(deletedObjects)+ - " registered="+registrar.registeredGlobalIDs()+" ]"; - } - private List idsForObjects( List objects ) - { - List result = new LinkedList(); - Iterator i = objects.iterator(); - while ( i.hasNext() ) result.add( globalIDForObject( i.next() ) ); - return result; - } - - // snapshots - - /** - * Returns a NSDictionary containing only the mutable properties - * for the specified object and deep clones of their values. - * Nulls are represented by NSNull.nullValue(). - */ - private byte[] takeSnapshot( Object anObject ) - { // System.out.println( "takeSnapshot: " + anObject ); - return KeyValueCodingUtilities.freeze( anObject, this, anObject, true ); - } - - /** - * Applies the map of properties and values to the - * specified object. Null values for properties must - * be represented by the NSNull.nullValue(). - * Posts a willChange event before applying changes. - */ - private void applySnapshot( byte[] aSnapshot, Object anObject ) - { - // must clone snapshot to avoid changing existing snapshot - NSDictionary values = convertSnapshotToDictionary( aSnapshot ); + // snapshots + + /** + * Returns a NSDictionary containing only the mutable properties for the + * specified object and deep clones of their values. Nulls are represented by + * NSNull.nullValue(). + */ + private byte[] takeSnapshot(Object anObject) { // System.out.println( "takeSnapshot: " + anObject ); + return KeyValueCodingUtilities.freeze(anObject, this, anObject, true); + } + + /** + * Applies the map of properties and values to the specified object. Null values + * for properties must be represented by the NSNull.nullValue(). Posts a + * willChange event before applying changes. + */ + private void applySnapshot(byte[] aSnapshot, Object anObject) { + // must clone snapshot to avoid changing existing snapshot + NSDictionary values = convertSnapshotToDictionary(aSnapshot); //System.out.println( "applySnapshot: " + aSnapshot + " : " + values ); //ignoreChanges = true; - willChange(); - KeyValueCodingUtilities.takeStoredValuesFromDictionary( anObject, values ); + willChange(); + KeyValueCodingUtilities.takeStoredValuesFromDictionary(anObject, values); //ignoreChanges = false; - } - - /** - * Snapshots are stored internally in binary format, - * but exposed to the user as NSDictionaries. - */ - private NSDictionary convertSnapshotToDictionary( byte[] aSnapshot ) - { - // get the object - Object clone = KeyValueCodingUtilities.thaw( aSnapshot, this, true ); - // get all keys for this object - EOClassDescription classDesc = - EOClassDescription.classDescriptionForClass( clone.getClass() ); - List keys = new LinkedList(); - keys.addAll( classDesc.attributeKeys() ); - keys.addAll( classDesc.toOneRelationshipKeys() ); - keys.addAll( classDesc.toManyRelationshipKeys() ); - - return KeyValueCodingUtilities.valuesForKeys( clone, keys ); - } - /** - * Creates a deep clone of the specified object. - * (Object.clone() only creates a shallow clone.) - * Returns null if operation fails. - */ - static private Object clone( - EOEditingContext aSourceContext, - Object aSource, - EOEditingContext aDestinationContext ) - { // System.out.println( "clone: " + aSource ); - return KeyValueCodingUtilities.clone( - aSourceContext, aSource, aDestinationContext ); - } - - /** - * Creates a deep clone of the specified object. - * but does not transpose references. This allows - * us to register an object before transposing - * references so that child objects will be able - * to resolve references to their parent. - * After recording the object, we copy the source - * object into the clone, which does transpose - * and resolve properly. - * Returns null if operation fails. - */ - static private Object registerClone( - EOGlobalID aGlobalID, - EOEditingContext aSourceContext, - Object aSource, - EOEditingContext aDestinationContext ) - { - // while we'd like to just transpose/clone at the same time - // we must record a clone without transposing: this - // avoids a endless loop if the object graph has a cycle - Object clone = KeyValueCodingUtilities.thaw( - KeyValueCodingUtilities.freeze( - aSource, aSourceContext, aSource, false ), - aDestinationContext, false ); - - aDestinationContext.recordObject( clone, aGlobalID ); - - // need to copy to transpose references into this context - // while preserving the same instance of the object - aDestinationContext.ignoreChanges = true; - copy( aSourceContext, aSource, aDestinationContext, clone ); - aDestinationContext.ignoreChanges = false; - - // return our clone - return clone; - } - - /** - * Copies values from one object to another. - * Returns the destination object, or throws exception - * if operation fails. - */ - static private Object copy( - EOEditingContext aSourceContext, - Object aSource, - EOEditingContext aDestinationContext, - Object aDestination ) - { // System.out.println( "copy: " ); - EOObserverCenter.notifyObserversObjectWillChange( aDestination ); - KeyValueCodingUtilities.copy( aSourceContext, aSource, aDestinationContext, aDestination ); - return aDestination; - } - - // process recent changes - - /** - * Called to notify observers of changes. - * Also calls runLater(). - */ - private void willChange() - { - EOObserverCenter.notifyObserversObjectWillChange( this ); - runLater(); - } - - /** - * Called to ensure that processRecentChanges - * will be called on the next event loop. - */ - private void runLater() - { - if ( ! willRunLater ) - { + } + + /** + * Snapshots are stored internally in binary format, but exposed to the user as + * NSDictionaries. + */ + private NSDictionary convertSnapshotToDictionary(byte[] aSnapshot) { + // get the object + Object clone = KeyValueCodingUtilities.thaw(aSnapshot, this, true); + // get all keys for this object + EOClassDescription classDesc = EOClassDescription.classDescriptionForClass(clone.getClass()); + List keys = new LinkedList(); + keys.addAll(classDesc.attributeKeys()); + keys.addAll(classDesc.toOneRelationshipKeys()); + keys.addAll(classDesc.toManyRelationshipKeys()); + + return KeyValueCodingUtilities.valuesForKeys(clone, keys); + } + + /** + * Creates a deep clone of the specified object. (Object.clone() only creates a + * shallow clone.) Returns null if operation fails. + */ + static private Object clone(EOEditingContext aSourceContext, Object aSource, EOEditingContext aDestinationContext) { // System.out.println( + // "clone: + // " + // + + // aSource + // ); + return KeyValueCodingUtilities.clone(aSourceContext, aSource, aDestinationContext); + } + + /** + * Creates a deep clone of the specified object. but does not transpose + * references. This allows us to register an object before transposing + * references so that child objects will be able to resolve references to their + * parent. After recording the object, we copy the source object into the clone, + * which does transpose and resolve properly. Returns null if operation fails. + */ + static private Object registerClone(EOGlobalID aGlobalID, EOEditingContext aSourceContext, Object aSource, + EOEditingContext aDestinationContext) { + // while we'd like to just transpose/clone at the same time + // we must record a clone without transposing: this + // avoids a endless loop if the object graph has a cycle + Object clone = KeyValueCodingUtilities.thaw( + KeyValueCodingUtilities.freeze(aSource, aSourceContext, aSource, false), aDestinationContext, false); + + aDestinationContext.recordObject(clone, aGlobalID); + + // need to copy to transpose references into this context + // while preserving the same instance of the object + aDestinationContext.ignoreChanges = true; + copy(aSourceContext, aSource, aDestinationContext, clone); + aDestinationContext.ignoreChanges = false; + + // return our clone + return clone; + } + + /** + * Copies values from one object to another. Returns the destination object, or + * throws exception if operation fails. + */ + static private Object copy(EOEditingContext aSourceContext, Object aSource, EOEditingContext aDestinationContext, + Object aDestination) { // System.out.println( "copy: " ); + EOObserverCenter.notifyObserversObjectWillChange(aDestination); + KeyValueCodingUtilities.copy(aSourceContext, aSource, aDestinationContext, aDestination); + return aDestination; + } + + // process recent changes + + /** + * Called to notify observers of changes. Also calls runLater(). + */ + private void willChange() { + EOObserverCenter.notifyObserversObjectWillChange(this); + runLater(); + } + + /** + * Called to ensure that processRecentChanges will be called on the next event + * loop. + */ + private void runLater() { + if (!willRunLater) { willRunLater = true; - NSRunLoop.currentRunLoop().performSelectorWithOrder( - runLaterSelector, this, null, EditingContextFlushChangesRunLoopOrdering, null ); + NSRunLoop.currentRunLoop().performSelectorWithOrder(runLaterSelector, this, null, + EditingContextFlushChangesRunLoopOrdering, null); } } - + /** - * This method is called by the event queue run loop - * and calls processRecentChanges. - * NOTE: This method is not part of the specification. - */ - public void flushRecentChanges( Object anObject ) - { + * This method is called by the event queue run loop and calls + * processRecentChanges. NOTE: This method is not part of the specification. + */ + public void flushRecentChanges(Object anObject) { //System.out.println( "EODelayedObserverQueue: running" ); - processRecentChanges(); + processRecentChanges(); willRunLater = false; } - - // inner classes - - /** - * Gatekeeper for all access to registered objects. - */ - static private class Registrar - { - EOEditingContext context; - NSMutableDictionary IDsToObjects; - NSMutableDictionary objectsToIDs; - NSMutableDictionary objectsToCommitSnapshots; - NSMutableDictionary objectsToCurrentSnapshots; - - ReferenceKey comparisonKey; //FIXME not thread safe! - - public Registrar( EOEditingContext aContext ) - { - context = aContext; - IDsToObjects = new NSMutableDictionary(); - objectsToIDs = new NSMutableDictionary(); - objectsToCommitSnapshots = new NSMutableDictionary(); - objectsToCurrentSnapshots = new NSMutableDictionary(); - comparisonKey = new ReferenceKey(); - } - - public NSArray registeredObjects() - { - return IDsToObjects.allValues(); - } - - public NSArray registeredGlobalIDs() - { - return IDsToObjects.allKeys(); - } - - public Object objectForGlobalID( EOGlobalID aGlobalID ) - { - return IDsToObjects.objectForKey( aGlobalID ); - } - - public EOGlobalID globalIDForObject( Object anObject ) - { - comparisonKey.set( anObject ); - return (EOGlobalID) objectsToIDs.objectForKey( comparisonKey ); - } - - public byte[] getCommitSnapshot( Object anObject ) - { - comparisonKey.set( anObject ); - return (byte[]) objectsToCommitSnapshots.objectForKey( comparisonKey ); - } - - public void setCommitSnapshot( Object anObject, byte[] aSnapshot ) - { - if ( aSnapshot == null ) - { - comparisonKey.set( anObject ); - objectsToCommitSnapshots.removeObjectForKey( comparisonKey ); - } - else - { - objectsToCommitSnapshots.setObjectForKey( - aSnapshot, new ReferenceKey( anObject ) ); - } - } - - public byte[] getCurrentSnapshot( Object anObject ) - { - comparisonKey.set( anObject ); - return (byte[]) objectsToCurrentSnapshots.objectForKey( comparisonKey ); - } - - public void setCurrentSnapshot( Object anObject, byte[] aSnapshot ) - { - if ( aSnapshot == null ) - { - comparisonKey.set( anObject ); - objectsToCurrentSnapshots.removeObjectForKey( comparisonKey ); - } - else - { - objectsToCurrentSnapshots.setObjectForKey( - aSnapshot, new ReferenceKey( anObject ) ); - } - } - - public void registerObject( Object anObject, EOGlobalID aGlobalID ) - { - IDsToObjects.setObjectForKey( anObject, aGlobalID ); - objectsToIDs.setObjectForKey( aGlobalID, new ReferenceKey( anObject ) ); - EOObserverCenter.addObserver( context, anObject ); - } - - public void forgetObject( Object anObject ) - { - comparisonKey.set( anObject ); - Object id = objectsToIDs.objectForKey( comparisonKey ); - IDsToObjects.removeObjectForKey( id ); - objectsToIDs.removeObjectForKey( comparisonKey ); - EOObserverCenter.removeObserver( context, anObject ); - } - - public void disposeSnapshots( Object anObject ) - { - setCommitSnapshot( anObject, null ); - setCurrentSnapshot( anObject, null ); - } - - } - - /** - * Registrar that uses only WeakReferences. - * Used if retainsRegisteredObjects is false. - */ - static private class WeakRegistrar extends Registrar - { - private WeakReferenceKey weakComparisonKey; //FIXME not thread safe! - - public WeakRegistrar( EOEditingContext aContext ) - { - super( aContext ); - weakComparisonKey = new WeakReferenceKey(); - } - - public NSArray registeredObjects() - { - Object object; - WeakReferenceKey weakKey; - NSMutableArray result = new NSMutableArray(); - Enumeration e = new NSArray( objectsToIDs.allKeys() ).objectEnumerator(); - while ( e.hasMoreElements() ) - { - weakKey = (WeakReferenceKey) e.nextElement(); - object = weakKey.get(); - if ( object != null ) - { - result.addObject( object ); - } - else - { - // object has been released: perform cleanup - disposeObject( null, weakKey ); - } - } - return result; - } - - public Object objectForGlobalID( EOGlobalID aGlobalID ) - { - WeakReference ref = (WeakReference) super.objectForGlobalID( aGlobalID ); - if ( ref == null ) return null; - Object result = ref.get(); - if ( result == null ) - { - // clean up manually - IDsToObjects.removeObjectForKey( aGlobalID ); - Iterator i = new LinkedList( objectsToIDs.allKeysForObject( ref ) ).iterator(); - while ( i.hasNext() ) - { - objectsToIDs.removeObjectForKey( i.next() ); - } - disposeSnapshots( aGlobalID ); - } - return result; - } - - public byte[] getCommitSnapshot( Object anObject ) - { - weakComparisonKey.set( anObject ); - return (byte[]) objectsToCommitSnapshots.objectForKey( weakComparisonKey ); - } - - public void setCommitSnapshot( Object anObject, byte[] aSnapshot ) - { - if ( aSnapshot == null ) - { - weakComparisonKey.set( anObject ); - objectsToCommitSnapshots.removeObjectForKey( weakComparisonKey ); - } - else - { - objectsToCommitSnapshots.setObjectForKey( - aSnapshot, new WeakReferenceKey( anObject ) ); - } - } - - public byte[] getCurrentSnapshot( Object anObject ) - { - weakComparisonKey.set( anObject ); - return (byte[]) objectsToCurrentSnapshots.objectForKey( weakComparisonKey ); - } - - public void setCurrentSnapshot( Object anObject, byte[] aSnapshot ) - { - if ( aSnapshot == null ) - { - weakComparisonKey.set( anObject ); - objectsToCurrentSnapshots.removeObjectForKey( weakComparisonKey ); - } - else - { - objectsToCurrentSnapshots.setObjectForKey( - aSnapshot, new WeakReferenceKey( anObject ) ); - } - } - - public void registerObject( Object anObject, EOGlobalID aGlobalID ) - { // new net.wotonomy.ui.swing.ReferenceInspector( anObject ); - IDsToObjects.setObjectForKey( new WeakReference( anObject ), aGlobalID ); - objectsToIDs.setObjectForKey( aGlobalID, new WeakReferenceKey( anObject ) ); - EOObserverCenter.addObserver( context, anObject ); - } - - public void forgetObject( Object anObject ) - { - disposeObject( anObject, null ); - } - - // must specify one or the other - private void disposeObject( Object anObject, WeakReferenceKey key ) - { - if ( key == null ) key = new WeakReferenceKey( anObject ); - EOGlobalID id = (EOGlobalID) objectsToIDs.objectForKey( key ); - if ( id != null ) IDsToObjects.removeObjectForKey( id ); - objectsToIDs.removeObjectForKey( key ); - disposeSnapshots( id ); - if ( anObject != null ) - { - EOObserverCenter.removeObserver( context, anObject ); - } - } - } - - /** - * Private class used to force a hashmap to - * perform key comparisons by reference. - */ - static private class ReferenceKey - { - int hashCode; - Object referent; - - public ReferenceKey() - { - referent = null; - hashCode = -1; - } - - public ReferenceKey( Object anObject ) - { - set( anObject ); - } - - public Object get() - { - return referent; - } - - public void set( Object anObject ) - { - referent = anObject; - hashCode = anObject.hashCode(); - } - - /** - * Returns the actual key's hash code. - */ - public int hashCode() - { - return hashCode; - } - - /** - * Compares by reference. - */ - public boolean equals( Object anObject ) - { - if ( anObject == this ) return true; - if ( anObject instanceof ReferenceKey ) - { - return ((ReferenceKey)anObject).get() == referent; - } - return false; - } - } - - /** - * Private class used to force a hashmap to - * perform key comparisons by reference. - */ - static private class WeakReferenceKey extends ReferenceKey - { - public WeakReferenceKey() - { - super(); - } - - public WeakReferenceKey( Object anObject ) - { - super( anObject ); - } - - public Object get() - { - return ((WeakReference)referent).get(); - } - - public void set( Object anObject ) - { - referent = new WeakReference( anObject ); - hashCode = anObject.hashCode(); - } - - /** - * Compares by reference. - */ - public boolean equals( Object anObject ) - { - if ( anObject == this ) return true; - if ( anObject instanceof ReferenceKey ) - { - return ((ReferenceKey)anObject).get() == get(); - } - return false; - } - } - - /** - * Key combining an object with a string. - * Object is compared by reference. - */ - static private class CompoundKey - { - private Object object; - private String string; - private int hashCode; - - /** - * Creates compound key. - * Neither name nor object may be null. - */ - public CompoundKey ( - Object anObject, String aString ) - { - object = anObject; - string = aString; - hashCode = object.hashCode() + string.hashCode(); - } - - public int hashCode() - { - return hashCode; - } - - public boolean equals( Object anObject ) - { - if ( anObject instanceof CompoundKey ) - { - CompoundKey key = (CompoundKey) anObject; - return ( ( key.object == object ) && ( key.string.equals( string ) ) ); - } - return false; - } - - public String toString() - { - return "[CompoundKey:"+object+":"+string+"]"; - } - } - - /** - * Used by EditingContext to delegate behavior to another class. - * Note that EditingContext doesn't require its delegates to implement - * this interface: rather, this interface defines the methods that - * EditingContext will attempt to invoke dynamically on its delegate. - * The delegate may choose to implement only a subset of the methods - * on the interface. - */ - public interface Delegate - { - /** - * Called after the editing context has completed merge operations - * on one or more objects after receiving an ObjectChangedInStore - * notification. - */ - void editingContextDidMergeChanges( - EOEditingContext anEditingContext ); - - /** - * Called by objectsWithFetchSpecification. - * If null, the editing context will pass the fetch specification - * on to its parent store, as normal. Otherwise, the context - * will use the returned array to service the request. - */ - NSArray editingContextShouldFetchObjects( - EOEditingContext anEditingContext, - EOFetchSpecification fetchSpecification ); - - /** - * Called to determine whether an object should be invalidated. - * Return false to prevent the object from being invalidated. - * Default is true. - */ - boolean editingContextShouldInvalidateObject( - EOEditingContext anEditingContext, - Object anObject, - EOGlobalID aGlobalID ); - - /** - * Called to determine whether the editing context should attempt - * to merge changes in the specified object that the parent store - * says has changed via an ObjectChangedInStore notification. - * Default is true. Return false if you wish to handle the merge - * yourself, by extracting the values in the object now and comparing - * them to the values when editingContextDidMergeChanges is called. - */ - boolean editingContextShouldMergeChangesForObject( - EOEditingContext anEditingContext, - Object anObject ); - - /** - * Returns whether the editing context should ask its message handler - * to display a message. Return false if the delegate will display the error. - * Default is true. - */ - boolean editingContextShouldPresentException( - EOEditingContext anEditingContext, - Throwable exception ); - - /** - * Returns whether the editing context should undo the most - * recent set of changes that resulted in a validation failure. - * Default is true. - */ - boolean editingContextShouldUndoUserActionsAfterFailure( - EOEditingContext anEditingContext ); - - /** - * Returns whether the editing context should validate the - * most recent set of changes. Default is true. - */ - boolean editingContextShouldValidateChanges( - EOEditingContext anEditingContext ); - - /** - * Called before the editing context saves its changes - * to the parent object store. - */ - void editingContextWillSaveChanges( - EOEditingContext anEditingContext ); - - } - - /** - * Editors register themselves with the editing context - * so that they may receive notification before the context - * commits changes. This is useful for associations whose - * components do not immediately commit their changes to - * the object they are editing. - */ - public interface Editor - { - /** - * Called before the editing context saves its changes - * to the parent object store. - */ - void editingContextWillSaveChanges( - EOEditingContext anEditingContext ); - - /** - * Called to determine whether this editor has changes - * that have not been committed to the object in the context. - */ - boolean editorHasChangesForEditingContext( - EOEditingContext anEditingContext ); - - } - - /** - * Used by EditingContext to delegate messaging handling to another class, - * typically the display group that has the currently active association. - * Note that EditingContext doesn't require its message handlers to implement - * this interface: rather, this interface defines the methods that - * EditingContext will attempt to invoke dynamically on its delegate. - * The delegate may choose to implement only a subset of the methods - * on the interface. - */ - public interface MessageHandler - { - /** - * Called to display a message for an error that occurred - * in the specified editing context. - */ - void editingContextPresentErrorMessage( - EOEditingContext anEditingContext, - String aMessage ); - - /** - * Called by the specified object store to determine whether - * fetching should continue, where count is the current count - * and limit is the limit as specified by the fetch specification. - * Default is false. - */ - boolean editingContextShouldContinueFetching( - EOEditingContext anEditingContext, - int count, - int limit, - EOObjectStore anObjectStore ); - - } - + + // inner classes + + /** + * Gatekeeper for all access to registered objects. + */ + static private class Registrar { + EOEditingContext context; + NSMutableDictionary IDsToObjects; + NSMutableDictionary objectsToIDs; + NSMutableDictionary objectsToCommitSnapshots; + NSMutableDictionary objectsToCurrentSnapshots; + + ReferenceKey comparisonKey; // FIXME not thread safe! + + public Registrar(EOEditingContext aContext) { + context = aContext; + IDsToObjects = new NSMutableDictionary(); + objectsToIDs = new NSMutableDictionary(); + objectsToCommitSnapshots = new NSMutableDictionary(); + objectsToCurrentSnapshots = new NSMutableDictionary(); + comparisonKey = new ReferenceKey(); + } + + public NSArray registeredObjects() { + return IDsToObjects.allValues(); + } + + public NSArray registeredGlobalIDs() { + return IDsToObjects.allKeys(); + } + + public Object objectForGlobalID(EOGlobalID aGlobalID) { + return IDsToObjects.objectForKey(aGlobalID); + } + + public EOGlobalID globalIDForObject(Object anObject) { + comparisonKey.set(anObject); + return (EOGlobalID) objectsToIDs.objectForKey(comparisonKey); + } + + public byte[] getCommitSnapshot(Object anObject) { + comparisonKey.set(anObject); + return (byte[]) objectsToCommitSnapshots.objectForKey(comparisonKey); + } + + public void setCommitSnapshot(Object anObject, byte[] aSnapshot) { + if (aSnapshot == null) { + comparisonKey.set(anObject); + objectsToCommitSnapshots.removeObjectForKey(comparisonKey); + } else { + objectsToCommitSnapshots.setObjectForKey(aSnapshot, new ReferenceKey(anObject)); + } + } + + public byte[] getCurrentSnapshot(Object anObject) { + comparisonKey.set(anObject); + return (byte[]) objectsToCurrentSnapshots.objectForKey(comparisonKey); + } + + public void setCurrentSnapshot(Object anObject, byte[] aSnapshot) { + if (aSnapshot == null) { + comparisonKey.set(anObject); + objectsToCurrentSnapshots.removeObjectForKey(comparisonKey); + } else { + objectsToCurrentSnapshots.setObjectForKey(aSnapshot, new ReferenceKey(anObject)); + } + } + + public void registerObject(Object anObject, EOGlobalID aGlobalID) { + IDsToObjects.setObjectForKey(anObject, aGlobalID); + objectsToIDs.setObjectForKey(aGlobalID, new ReferenceKey(anObject)); + EOObserverCenter.addObserver(context, anObject); + } + + public void forgetObject(Object anObject) { + comparisonKey.set(anObject); + Object id = objectsToIDs.objectForKey(comparisonKey); + IDsToObjects.removeObjectForKey(id); + objectsToIDs.removeObjectForKey(comparisonKey); + EOObserverCenter.removeObserver(context, anObject); + } + + public void disposeSnapshots(Object anObject) { + setCommitSnapshot(anObject, null); + setCurrentSnapshot(anObject, null); + } + + } + + /** + * Registrar that uses only WeakReferences. Used if retainsRegisteredObjects is + * false. + */ + static private class WeakRegistrar extends Registrar { + private WeakReferenceKey weakComparisonKey; // FIXME not thread safe! + + public WeakRegistrar(EOEditingContext aContext) { + super(aContext); + weakComparisonKey = new WeakReferenceKey(); + } + + public NSArray registeredObjects() { + Object object; + WeakReferenceKey weakKey; + NSMutableArray result = new NSMutableArray(); + Enumeration e = new NSArray(objectsToIDs.allKeys()).objectEnumerator(); + while (e.hasMoreElements()) { + weakKey = (WeakReferenceKey) e.nextElement(); + object = weakKey.get(); + if (object != null) { + result.addObject(object); + } else { + // object has been released: perform cleanup + disposeObject(null, weakKey); + } + } + return result; + } + + public Object objectForGlobalID(EOGlobalID aGlobalID) { + WeakReference ref = (WeakReference) super.objectForGlobalID(aGlobalID); + if (ref == null) + return null; + Object result = ref.get(); + if (result == null) { + // clean up manually + IDsToObjects.removeObjectForKey(aGlobalID); + Iterator i = new LinkedList(objectsToIDs.allKeysForObject(ref)).iterator(); + while (i.hasNext()) { + objectsToIDs.removeObjectForKey(i.next()); + } + disposeSnapshots(aGlobalID); + } + return result; + } + + public byte[] getCommitSnapshot(Object anObject) { + weakComparisonKey.set(anObject); + return (byte[]) objectsToCommitSnapshots.objectForKey(weakComparisonKey); + } + + public void setCommitSnapshot(Object anObject, byte[] aSnapshot) { + if (aSnapshot == null) { + weakComparisonKey.set(anObject); + objectsToCommitSnapshots.removeObjectForKey(weakComparisonKey); + } else { + objectsToCommitSnapshots.setObjectForKey(aSnapshot, new WeakReferenceKey(anObject)); + } + } + + public byte[] getCurrentSnapshot(Object anObject) { + weakComparisonKey.set(anObject); + return (byte[]) objectsToCurrentSnapshots.objectForKey(weakComparisonKey); + } + + public void setCurrentSnapshot(Object anObject, byte[] aSnapshot) { + if (aSnapshot == null) { + weakComparisonKey.set(anObject); + objectsToCurrentSnapshots.removeObjectForKey(weakComparisonKey); + } else { + objectsToCurrentSnapshots.setObjectForKey(aSnapshot, new WeakReferenceKey(anObject)); + } + } + + public void registerObject(Object anObject, EOGlobalID aGlobalID) { // new + // net.wotonomy.ui.swing.ReferenceInspector( + // anObject ); + IDsToObjects.setObjectForKey(new WeakReference(anObject), aGlobalID); + objectsToIDs.setObjectForKey(aGlobalID, new WeakReferenceKey(anObject)); + EOObserverCenter.addObserver(context, anObject); + } + + public void forgetObject(Object anObject) { + disposeObject(anObject, null); + } + + // must specify one or the other + private void disposeObject(Object anObject, WeakReferenceKey key) { + if (key == null) + key = new WeakReferenceKey(anObject); + EOGlobalID id = (EOGlobalID) objectsToIDs.objectForKey(key); + if (id != null) + IDsToObjects.removeObjectForKey(id); + objectsToIDs.removeObjectForKey(key); + disposeSnapshots(id); + if (anObject != null) { + EOObserverCenter.removeObserver(context, anObject); + } + } + } + + /** + * Private class used to force a hashmap to perform key comparisons by + * reference. + */ + static private class ReferenceKey { + int hashCode; + Object referent; + + public ReferenceKey() { + referent = null; + hashCode = -1; + } + + public ReferenceKey(Object anObject) { + set(anObject); + } + + public Object get() { + return referent; + } + + public void set(Object anObject) { + referent = anObject; + hashCode = anObject.hashCode(); + } + + /** + * Returns the actual key's hash code. + */ + public int hashCode() { + return hashCode; + } + + /** + * Compares by reference. + */ + public boolean equals(Object anObject) { + if (anObject == this) + return true; + if (anObject instanceof ReferenceKey) { + return ((ReferenceKey) anObject).get() == referent; + } + return false; + } + } + + /** + * Private class used to force a hashmap to perform key comparisons by + * reference. + */ + static private class WeakReferenceKey extends ReferenceKey { + public WeakReferenceKey() { + super(); + } + + public WeakReferenceKey(Object anObject) { + super(anObject); + } + + public Object get() { + return ((WeakReference) referent).get(); + } + + public void set(Object anObject) { + referent = new WeakReference(anObject); + hashCode = anObject.hashCode(); + } + + /** + * Compares by reference. + */ + public boolean equals(Object anObject) { + if (anObject == this) + return true; + if (anObject instanceof ReferenceKey) { + return ((ReferenceKey) anObject).get() == get(); + } + return false; + } + } + + /** + * Key combining an object with a string. Object is compared by reference. + */ + static private class CompoundKey { + private Object object; + private String string; + private int hashCode; + + /** + * Creates compound key. Neither name nor object may be null. + */ + public CompoundKey(Object anObject, String aString) { + object = anObject; + string = aString; + hashCode = object.hashCode() + string.hashCode(); + } + + public int hashCode() { + return hashCode; + } + + public boolean equals(Object anObject) { + if (anObject instanceof CompoundKey) { + CompoundKey key = (CompoundKey) anObject; + return ((key.object == object) && (key.string.equals(string))); + } + return false; + } + + public String toString() { + return "[CompoundKey:" + object + ":" + string + "]"; + } + } + + /** + * Used by EditingContext to delegate behavior to another class. Note that + * EditingContext doesn't require its delegates to implement this interface: + * rather, this interface defines the methods that EditingContext will attempt + * to invoke dynamically on its delegate. The delegate may choose to implement + * only a subset of the methods on the interface. + */ + public interface Delegate { + /** + * Called after the editing context has completed merge operations on one or + * more objects after receiving an ObjectChangedInStore notification. + */ + void editingContextDidMergeChanges(EOEditingContext anEditingContext); + + /** + * Called by objectsWithFetchSpecification. If null, the editing context will + * pass the fetch specification on to its parent store, as normal. Otherwise, + * the context will use the returned array to service the request. + */ + NSArray editingContextShouldFetchObjects(EOEditingContext anEditingContext, + EOFetchSpecification fetchSpecification); + + /** + * Called to determine whether an object should be invalidated. Return false to + * prevent the object from being invalidated. Default is true. + */ + boolean editingContextShouldInvalidateObject(EOEditingContext anEditingContext, Object anObject, + EOGlobalID aGlobalID); + + /** + * Called to determine whether the editing context should attempt to merge + * changes in the specified object that the parent store says has changed via an + * ObjectChangedInStore notification. Default is true. Return false if you wish + * to handle the merge yourself, by extracting the values in the object now and + * comparing them to the values when editingContextDidMergeChanges is called. + */ + boolean editingContextShouldMergeChangesForObject(EOEditingContext anEditingContext, Object anObject); + + /** + * Returns whether the editing context should ask its message handler to display + * a message. Return false if the delegate will display the error. Default is + * true. + */ + boolean editingContextShouldPresentException(EOEditingContext anEditingContext, Throwable exception); + + /** + * Returns whether the editing context should undo the most recent set of + * changes that resulted in a validation failure. Default is true. + */ + boolean editingContextShouldUndoUserActionsAfterFailure(EOEditingContext anEditingContext); + + /** + * Returns whether the editing context should validate the most recent set of + * changes. Default is true. + */ + boolean editingContextShouldValidateChanges(EOEditingContext anEditingContext); + + /** + * Called before the editing context saves its changes to the parent object + * store. + */ + void editingContextWillSaveChanges(EOEditingContext anEditingContext); + + } + + /** + * Editors register themselves with the editing context so that they may receive + * notification before the context commits changes. This is useful for + * associations whose components do not immediately commit their changes to the + * object they are editing. + */ + public interface Editor { + /** + * Called before the editing context saves its changes to the parent object + * store. + */ + void editingContextWillSaveChanges(EOEditingContext anEditingContext); + + /** + * Called to determine whether this editor has changes that have not been + * committed to the object in the context. + */ + boolean editorHasChangesForEditingContext(EOEditingContext anEditingContext); + + } + + /** + * Used by EditingContext to delegate messaging handling to another class, + * typically the display group that has the currently active association. Note + * that EditingContext doesn't require its message handlers to implement this + * interface: rather, this interface defines the methods that EditingContext + * will attempt to invoke dynamically on its delegate. The delegate may choose + * to implement only a subset of the methods on the interface. + */ + public interface MessageHandler { + /** + * Called to display a message for an error that occurred in the specified + * editing context. + */ + void editingContextPresentErrorMessage(EOEditingContext anEditingContext, String aMessage); + + /** + * Called by the specified object store to determine whether fetching should + * continue, where count is the current count and limit is the limit as + * specified by the fetch specification. Default is false. + */ + boolean editingContextShouldContinueFetching(EOEditingContext anEditingContext, int count, int limit, + EOObjectStore anObjectStore); + + } + } /* - * $Log$ - * Revision 1.2 2006/02/16 16:47:14 cgruber - * Move some classes in to "internal" packages and re-work imports, etc. + * $Log$ Revision 1.2 2006/02/16 16:47:14 cgruber Move some classes in to + * "internal" packages and re-work imports, etc. * - * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. + * Also use UnsupportedOperationExceptions where appropriate, instead of + * WotonomyExceptions. * - * Revision 1.1 2006/02/16 13:19:57 cgruber - * Check in all sources in eclipse-friendly maven-enabled packages. + * Revision 1.1 2006/02/16 13:19:57 cgruber Check in all sources in + * eclipse-friendly maven-enabled packages. * - * Revision 1.86 2003/12/18 15:37:38 mpowers - * Changes to retain ability to work with objects that don't necessarily - * implement EOEnterpriseObject. I would still like to preserve this case - * for general usage, however the access package is free to assume that - * those objects will be EOs and cast appropriately. + * Revision 1.86 2003/12/18 15:37:38 mpowers Changes to retain ability to work + * with objects that don't necessarily implement EOEnterpriseObject. I would + * still like to preserve this case for general usage, however the access + * package is free to assume that those objects will be EOs and cast + * appropriately. * - * Revision 1.85 2003/08/19 01:53:12 chochos - * EOObjectStore had some incompatible return types (Object instead of EOEnterpriseObject, in fault methods mostly). It's internally consistent but I hope it doesn't break anything based on this, even though fault methods mostly throw exceptions for now. + * Revision 1.85 2003/08/19 01:53:12 chochos EOObjectStore had some incompatible + * return types (Object instead of EOEnterpriseObject, in fault methods mostly). + * It's internally consistent but I hope it doesn't break anything based on + * this, even though fault methods mostly throw exceptions for now. * - * Revision 1.84 2003/08/06 23:07:52 chochos - * general code cleanup (mostly, removing unused imports) + * Revision 1.84 2003/08/06 23:07:52 chochos general code cleanup (mostly, + * removing unused imports) * - * Revision 1.83 2003/02/13 15:24:33 mpowers - * hasChanges is now derived, not tracked. - * refaultObject now more consistently removes object from updated list. + * Revision 1.83 2003/02/13 15:24:33 mpowers hasChanges is now derived, not + * tracked. refaultObject now more consistently removes object from updated + * list. * - * Revision 1.82 2002/12/16 15:46:00 mpowers - * Major refactoring to implement setInstancesRetainRegisteredObjects(). + * Revision 1.82 2002/12/16 15:46:00 mpowers Major refactoring to implement + * setInstancesRetainRegisteredObjects(). * - * Revision 1.81 2002/11/18 22:10:58 mpowers - * Now resetting hasChanges flag on reset. + * Revision 1.81 2002/11/18 22:10:58 mpowers Now resetting hasChanges flag on + * reset. * - * Revision 1.80 2002/10/24 21:15:33 mpowers - * New implementations of NSArray and subclasses. + * Revision 1.80 2002/10/24 21:15:33 mpowers New implementations of NSArray and + * subclasses. * - * Revision 1.79 2002/10/24 18:18:12 mpowers - * NSArray's are now considered read-only, so we can return our internal - * representation to reduce unnecessary object allocation. + * Revision 1.79 2002/10/24 18:18:12 mpowers NSArray's are now considered + * read-only, so we can return our internal representation to reduce unnecessary + * object allocation. * - * Revision 1.78 2002/06/21 21:44:33 mpowers - * No longer marking deleted objects as updated (thanks to dwang). + * Revision 1.78 2002/06/21 21:44:33 mpowers No longer marking deleted objects + * as updated (thanks to dwang). * - * Revision 1.77 2002/05/20 15:10:17 mpowers - * No longer refaulting if delegate does not wish to handle the merge. + * Revision 1.77 2002/05/20 15:10:17 mpowers No longer refaulting if delegate + * does not wish to handle the merge. * - * Revision 1.76 2002/03/26 21:46:06 mpowers - * Contributing EditingContext as a java-friendly convenience. + * Revision 1.76 2002/03/26 21:46:06 mpowers Contributing EditingContext as a + * java-friendly convenience. * - * Revision 1.75 2002/03/06 16:14:57 mpowers - * More attempts at ignoring update conflicts that come from ourself. + * Revision 1.75 2002/03/06 16:14:57 mpowers More attempts at ignoring update + * conflicts that come from ourself. * - * Revision 1.74 2002/02/21 21:57:50 mpowers - * Implemented default merge behavior. + * Revision 1.74 2002/02/21 21:57:50 mpowers Implemented default merge behavior. * - * Revision 1.73 2002/02/20 16:46:54 mpowers - * Implemented better support for EOEditingContext.Delegate. + * Revision 1.73 2002/02/20 16:46:54 mpowers Implemented better support for + * EOEditingContext.Delegate. * - * Revision 1.70 2002/02/19 22:26:05 mpowers - * Implemented EOEditingContext.MessageHandler support. + * Revision 1.70 2002/02/19 22:26:05 mpowers Implemented + * EOEditingContext.MessageHandler support. * - * Revision 1.69 2002/02/19 16:33:42 mpowers - * Implemented support for EditingContext.Editor + * Revision 1.69 2002/02/19 16:33:42 mpowers Implemented support for + * EditingContext.Editor * - * Revision 1.68 2002/02/13 22:00:34 mpowers - * Fixed: invalidateAllObjects tries to invalidate inserted objects, - * typically causing class cast exceptions involving EOTemporaryGlobalID. + * Revision 1.68 2002/02/13 22:00:34 mpowers Fixed: invalidateAllObjects tries + * to invalidate inserted objects, typically causing class cast exceptions + * involving EOTemporaryGlobalID. * - * Revision 1.67 2002/02/06 21:15:35 mpowers - * No longer refaulting a dirty object when we receive an invalidation notif. + * Revision 1.67 2002/02/06 21:15:35 mpowers No longer refaulting a dirty object + * when we receive an invalidation notif. * - * Revision 1.66 2002/01/08 19:31:03 mpowers - * refaultObject now correctly refaults the object. + * Revision 1.66 2002/01/08 19:31:03 mpowers refaultObject now correctly + * refaults the object. * - * Revision 1.65 2001/12/20 18:56:15 mpowers - * Refinements to snapshotting and calling processRecentChanges. + * Revision 1.65 2001/12/20 18:56:15 mpowers Refinements to snapshotting and + * calling processRecentChanges. * - * Revision 1.64 2001/12/10 15:11:41 mpowers - * Now only tracking a commit snapshot after an object has been modified. + * Revision 1.64 2001/12/10 15:11:41 mpowers Now only tracking a commit snapshot + * after an object has been modified. * - * Revision 1.63 2001/11/14 00:08:10 mpowers - * Now marking context changed when objects are inserted or deleted - * and when child contexts save their changes into this context. + * Revision 1.63 2001/11/14 00:08:10 mpowers Now marking context changed when + * objects are inserted or deleted and when child contexts save their changes + * into this context. * - * Revision 1.62 2001/11/07 14:49:31 mpowers - * invalidateAllObjects now handles objects manually discarded in the course - * of invalidation. + * Revision 1.62 2001/11/07 14:49:31 mpowers invalidateAllObjects now handles + * objects manually discarded in the course of invalidation. * - * Revision 1.61 2001/10/26 20:02:49 mpowers - * No longer using NSNotificationQueue: all notifications are posted immed. + * Revision 1.61 2001/10/26 20:02:49 mpowers No longer using + * NSNotificationQueue: all notifications are posted immed. * - * Revision 1.60 2001/10/26 18:37:50 mpowers - * Now using NSRunLoop to correctly flush recent changes before delayed - * observers and AWT events. + * Revision 1.60 2001/10/26 18:37:50 mpowers Now using NSRunLoop to correctly + * flush recent changes before delayed observers and AWT events. * - * Revision 1.59 2001/10/23 22:29:59 mpowers - * Now posting notifications immediately. - * Recent changes are flushed at ObserverPrioritySixth, soon to change. + * Revision 1.59 2001/10/23 22:29:59 mpowers Now posting notifications + * immediately. Recent changes are flushed at ObserverPrioritySixth, soon to + * change. * - * Revision 1.58 2001/09/10 14:16:51 mpowers - * EditingContexts now relay InvalidatedAllObjectsInStore notifications. + * Revision 1.58 2001/09/10 14:16:51 mpowers EditingContexts now relay + * InvalidatedAllObjectsInStore notifications. * - * Revision 1.57 2001/06/18 14:11:15 mpowers - * Inserting a deleted object simply cancels the delete operation. + * Revision 1.57 2001/06/18 14:11:15 mpowers Inserting a deleted object simply + * cancels the delete operation. * - * Revision 1.56 2001/06/07 22:07:59 mpowers - * Now handling delete notifications before update notifications. - * Eliminated the case where deleted objects were also being put - * on the updated list when notifying child contexts. + * Revision 1.56 2001/06/07 22:07:59 mpowers Now handling delete notifications + * before update notifications. Eliminated the case where deleted objects were + * also being put on the updated list when notifying child contexts. * - * Revision 1.55 2001/05/18 21:04:33 mpowers - * Reimplemented EditingContext.initializeObject. + * Revision 1.55 2001/05/18 21:04:33 mpowers Reimplemented + * EditingContext.initializeObject. * - * Revision 1.54 2001/05/05 23:05:42 mpowers - * Implemented Array Faults. + * Revision 1.54 2001/05/05 23:05:42 mpowers Implemented Array Faults. * - * Revision 1.53 2001/05/05 15:00:06 mpowers - * Tested load-on-demand: still works. - * Now using registerClone for consistency. - * Editing context is temporarily posting notification on objectWillChange. + * Revision 1.53 2001/05/05 15:00:06 mpowers Tested load-on-demand: still works. + * Now using registerClone for consistency. Editing context is temporarily + * posting notification on objectWillChange. * - * Revision 1.52 2001/05/05 14:11:48 mpowers - * Implemented registerClone. + * Revision 1.52 2001/05/05 14:11:48 mpowers Implemented registerClone. * - * Revision 1.51 2001/05/04 16:57:56 mpowers - * Now correctly transposing references to editing contexts when - * cloning/copying between editing contexts. + * Revision 1.51 2001/05/04 16:57:56 mpowers Now correctly transposing + * references to editing contexts when cloning/copying between editing contexts. * - * Revision 1.50 2001/05/02 17:58:41 mpowers - * Removed debugging code, added comments. + * Revision 1.50 2001/05/02 17:58:41 mpowers Removed debugging code, added + * comments. * - * Revision 1.49 2001/05/02 15:47:40 mpowers - * Fixed the pernicious problem with reverts: recordObject was recording - * a snapshot of the clone before the transposition-copy happened, - * so the revert object would lose all of its transposed relationships. + * Revision 1.49 2001/05/02 15:47:40 mpowers Fixed the pernicious problem with + * reverts: recordObject was recording a snapshot of the clone before the + * transposition-copy happened, so the revert object would lose all of its + * transposed relationships. * - * Revision 1.48 2001/05/02 12:39:05 mpowers - * Fixed a nasty problem with transpose-cloning and faultForGlobalID. - * Now we're forced to create a deep clone, registered it, and then - * transpose it after it has been registered. + * Revision 1.48 2001/05/02 12:39:05 mpowers Fixed a nasty problem with + * transpose-cloning and faultForGlobalID. Now we're forced to create a deep + * clone, registered it, and then transpose it after it has been registered. * - * Revision 1.47 2001/04/30 13:15:24 mpowers - * Child contexts re-initializing objects invalidated in parent now - * propery transpose relationships. + * Revision 1.47 2001/04/30 13:15:24 mpowers Child contexts re-initializing + * objects invalidated in parent now propery transpose relationships. * - * Revision 1.46 2001/04/29 22:02:45 mpowers - * Work on id transposing between editing contexts. + * Revision 1.46 2001/04/29 22:02:45 mpowers Work on id transposing between + * editing contexts. * - * Revision 1.45 2001/04/29 02:29:31 mpowers - * Debugging relationship faulting. + * Revision 1.45 2001/04/29 02:29:31 mpowers Debugging relationship faulting. * - * Revision 1.44 2001/04/28 16:18:44 mpowers - * Implementing relationships. + * Revision 1.44 2001/04/28 16:18:44 mpowers Implementing relationships. * - * Revision 1.43 2001/04/28 14:12:23 mpowers - * Refactored cloning/copying into KeyValueCodingUtilities. + * Revision 1.43 2001/04/28 14:12:23 mpowers Refactored cloning/copying into + * KeyValueCodingUtilities. * - * Revision 1.42 2001/04/27 00:27:11 mpowers - * Provided description to not-implemented exceptions. + * Revision 1.42 2001/04/27 00:27:11 mpowers Provided description to + * not-implemented exceptions. * - * Revision 1.41 2001/04/26 01:16:44 mpowers - * Major bug fix so we no longer accumulate objects in the all objects - * list with every InvalidateAllObjectsInStore. + * Revision 1.41 2001/04/26 01:16:44 mpowers Major bug fix so we no longer + * accumulate objects in the all objects list with every + * InvalidateAllObjectsInStore. * - * Revision 1.40 2001/04/21 23:07:49 mpowers - * Now only broadcasts notifications if there's actually a change. + * Revision 1.40 2001/04/21 23:07:49 mpowers Now only broadcasts notifications + * if there's actually a change. * - * Revision 1.39 2001/04/13 16:33:11 mpowers - * Corrected the refaulting behavior. + * Revision 1.39 2001/04/13 16:33:11 mpowers Corrected the refaulting behavior. * - * Revision 1.38 2001/04/09 21:42:10 mpowers - * Debugging and optimizing notifications. + * Revision 1.38 2001/04/09 21:42:10 mpowers Debugging and optimizing + * notifications. * - * Revision 1.37 2001/04/08 20:59:47 mpowers - * objectsForFetchSpecification now relies on faultForGlobalID. + * Revision 1.37 2001/04/08 20:59:47 mpowers objectsForFetchSpecification now + * relies on faultForGlobalID. * - * Revision 1.36 2001/04/03 20:36:01 mpowers - * Fixed refaulting/reverting/invalidating to be self-consistent. + * Revision 1.36 2001/04/03 20:36:01 mpowers Fixed + * refaulting/reverting/invalidating to be self-consistent. * - * Revision 1.35 2001/03/29 03:29:49 mpowers - * Now using KeyValueCoding and Support instead of Introspector. + * Revision 1.35 2001/03/29 03:29:49 mpowers Now using KeyValueCoding and + * Support instead of Introspector. * - * Revision 1.34 2001/03/28 14:06:29 mpowers - * Implemented snapshots. Revert now uses snapshots. + * Revision 1.34 2001/03/28 14:06:29 mpowers Implemented snapshots. Revert now + * uses snapshots. * - * Revision 1.33 2001/03/20 23:20:33 mpowers - * invalidating all objects now sets the dirty flag to false. + * Revision 1.33 2001/03/20 23:20:33 mpowers invalidating all objects now sets + * the dirty flag to false. * - * Revision 1.32 2001/03/19 21:44:36 mpowers - * Reverts reinitialize for now. + * Revision 1.32 2001/03/19 21:44:36 mpowers Reverts reinitialize for now. * Testing for inserted objects instead of temp id when invalidating object. * - * Revision 1.31 2001/03/15 21:10:26 mpowers - * Implemented global id re-registration for newly saved inserts. + * Revision 1.31 2001/03/15 21:10:26 mpowers Implemented global id + * re-registration for newly saved inserts. * - * Revision 1.30 2001/03/13 21:41:34 mpowers - * Broadcasting willChange for any change to hasChanges. - * Fixed major bug with inserted objects treated as updated objects - * in child display groups. + * Revision 1.30 2001/03/13 21:41:34 mpowers Broadcasting willChange for any + * change to hasChanges. Fixed major bug with inserted objects treated as + * updated objects in child display groups. * - * Revision 1.29 2001/03/09 22:10:30 mpowers - * Fine tuned initializeObject. + * Revision 1.29 2001/03/09 22:10:30 mpowers Fine tuned initializeObject. * - * Revision 1.28 2001/03/06 23:23:55 mpowers - * objectForGlobalID now returns null if not found. - * objectsForFetchSpecification again does things the old way, for now. + * Revision 1.28 2001/03/06 23:23:55 mpowers objectForGlobalID now returns null + * if not found. objectsForFetchSpecification again does things the old way, for + * now. * - * Revision 1.27 2001/03/02 16:31:45 mpowers - * Trying to better handle fetches from child contexts. - * No longer trying to invalidate temporary objects. + * Revision 1.27 2001/03/02 16:31:45 mpowers Trying to better handle fetches + * from child contexts. No longer trying to invalidate temporary objects. * - * Revision 1.26 2001/02/28 16:25:19 mpowers - * Now calling globalIDForObject internally. + * Revision 1.26 2001/02/28 16:25:19 mpowers Now calling globalIDForObject + * internally. * - * Revision 1.25 2001/02/27 17:36:55 mpowers - * Objects inserted from child now preserve the existing temporary id. + * Revision 1.25 2001/02/27 17:36:55 mpowers Objects inserted from child now + * preserve the existing temporary id. * - * Revision 1.24 2001/02/27 02:11:17 mpowers - * Now throwing exception when cloning fails. - * Removed debugging printlns. + * Revision 1.24 2001/02/27 02:11:17 mpowers Now throwing exception when cloning + * fails. Removed debugging printlns. * - * Revision 1.23 2001/02/26 22:41:51 mpowers - * Implemented null placeholder classes. - * Duplicator now uses NSNull. - * No longer catching base exception class. + * Revision 1.23 2001/02/26 22:41:51 mpowers Implemented null placeholder + * classes. Duplicator now uses NSNull. No longer catching base exception class. * - * Revision 1.22 2001/02/26 21:18:45 mpowers - * Now marking edited objects from child contexts that were not already - * recorded in parent as changed in saveChangesInEditingContext. + * Revision 1.22 2001/02/26 21:18:45 mpowers Now marking edited objects from + * child contexts that were not already recorded in parent as changed in + * saveChangesInEditingContext. * - * Revision 1.21 2001/02/26 15:53:22 mpowers - * Fine-tuning notification firing. + * Revision 1.21 2001/02/26 15:53:22 mpowers Fine-tuning notification firing. * Child display groups now update properly after parent save or invalidate. * - * Revision 1.20 2001/02/24 17:03:22 mpowers - * Implemented the notification queue, and changed editing context to use it. + * Revision 1.20 2001/02/24 17:03:22 mpowers Implemented the notification queue, + * and changed editing context to use it. * - * Revision 1.19 2001/02/23 23:44:15 mpowers - * Fine-tuning notification handling. + * Revision 1.19 2001/02/23 23:44:15 mpowers Fine-tuning notification handling. * - * Revision 1.18 2001/02/22 22:56:57 mpowers - * Only refaulting edited objects on parent store invalidateAll. + * Revision 1.18 2001/02/22 22:56:57 mpowers Only refaulting edited objects on + * parent store invalidateAll. * - * Revision 1.17 2001/02/22 20:54:39 mpowers - * Implemented notification handling. + * Revision 1.17 2001/02/22 20:54:39 mpowers Implemented notification handling. * - * Revision 1.16 2001/02/21 22:10:55 mpowers - * Editing context is now posting appropriate notifications. + * Revision 1.16 2001/02/21 22:10:55 mpowers Editing context is now posting + * appropriate notifications. * - * Revision 1.15 2001/02/21 21:17:32 mpowers - * Now retaining a reference to the recent changes observer. - * Better documented need to retain reference. - * Started implementing notifications. + * Revision 1.15 2001/02/21 21:17:32 mpowers Now retaining a reference to the + * recent changes observer. Better documented need to retain reference. Started + * implementing notifications. * - * Revision 1.14 2001/02/20 17:24:22 mpowers - * Now using reference keys in objectToID. + * Revision 1.14 2001/02/20 17:24:22 mpowers Now using reference keys in + * objectToID. * - * Revision 1.13 2001/02/20 16:45:36 mpowers - * Child data sources now accept a data source instead of an editing context - * for more flexibility. Child data sources now forward relationship - * methods to parent source. + * Revision 1.13 2001/02/20 16:45:36 mpowers Child data sources now accept a + * data source instead of an editing context for more flexibility. Child data + * sources now forward relationship methods to parent source. * - * Revision 1.12 2001/02/16 22:51:29 mpowers - * Now deep-cloning objects passed between editing contexts. + * Revision 1.12 2001/02/16 22:51:29 mpowers Now deep-cloning objects passed + * between editing contexts. * - * Revision 1.11 2001/02/16 18:34:19 mpowers - * Implementing nested contexts. + * Revision 1.11 2001/02/16 18:34:19 mpowers Implementing nested contexts. * - * Revision 1.9 2001/02/15 21:13:30 mpowers - * First draft implementation is complete. Now on to debugging. + * Revision 1.9 2001/02/15 21:13:30 mpowers First draft implementation is + * complete. Now on to debugging. * - * Revision 1.7 2001/02/13 23:24:29 mpowers - * Implementing more of editing context. + * Revision 1.7 2001/02/13 23:24:29 mpowers Implementing more of editing + * context. * - * Revision 1.4 2001/02/09 22:09:34 mpowers - * Completed implementation of EOObjectStore. + * Revision 1.4 2001/02/09 22:09:34 mpowers Completed implementation of + * EOObjectStore. * - * Revision 1.3 2001/02/06 15:24:11 mpowers - * Widened parameters on abstract method to fix build. + * Revision 1.3 2001/02/06 15:24:11 mpowers Widened parameters on abstract + * method to fix build. * - * Revision 1.2 2001/02/05 03:45:37 mpowers - * Starting work on EOEditingContext. + * Revision 1.2 2001/02/05 03:45:37 mpowers Starting work on EOEditingContext. * - * Revision 1.1.1.1 2000/12/21 15:46:42 mpowers - * Contributing wotonomy. + * Revision 1.1.1.1 2000/12/21 15:46:42 mpowers Contributing wotonomy. * */ - - |
