summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java
diff options
context:
space:
mode:
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.java3247
1 files changed, 3247 insertions, 0 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
new file mode 100644
index 0000000..b01727d
--- /dev/null
+++ b/projects/net.wotonomy.persistence/src/main/java/net/wotonomy/control/EOEditingContext.java
@@ -0,0 +1,3247 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, see http://www.gnu.org
+*/
+
+package net.wotonomy.control;
+
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSNotification;
+import net.wotonomy.foundation.NSNotificationCenter;
+import net.wotonomy.foundation.NSRunLoop;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+// swing dependency for undo manager
+//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;
+// 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
+// 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 );
+//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();
+
+ int i;
+ // remove from added objects if necessary
+ 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
+ {
+ 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.");
+ }
+*/
+
+ /**
+ * 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;
+
+ // 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 ()
+ {
+//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;
+//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;
+ }
+
+/*
+ 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;
+ }
+
+ // 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 );
+//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 )
+ {
+ willRunLater = true;
+ 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 )
+ {
+//System.out.println( "EODelayedObserverQueue: running" );
+ 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 );
+
+ }
+
+}
+
+/*
+ * $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.
+ *
+ * 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.
+ * 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.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.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.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.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.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.
+ * 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.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.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.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.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.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.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.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.1.1.1 2000/12/21 15:46:42 mpowers
+ * Contributing wotonomy.
+ *
+ */
+
+