/* Implementation of the NSManagedObjectContext class for the GNUstep Core Data framework. Copyright (C) 2005 Free Software Foundation, Inc. Written by: Saso Kiselkov Date: August 2005 This file is part of the GNUstep Core Data framework. 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. */ #import "CoreDataHeaders.h" id NSErrorMergePolicy = nil, NSMergeByPropertyStoreTrumpMergePolicy = nil, NSMergeByPropertyObjectTrumpMergePolicy = nil, NSOverwriteMergePolicy = nil, NSRollbackMergePolicy = nil; /** * Deregisters `observer' from all the managed objects in `objects'. * In more detail, this function traverses through all the objects * and all their properties and executes removeObserver:forKeyPath: * on every property name. */ static void RemoveKVOSetupFromObjects(id observer, NSSet * objects) { NSEnumerator * e = [objects objectEnumerator]; NSManagedObject * object; while ((object = [e nextObject]) != nil) { NSEntityDescription * entity; // traverse the entity hierarchy correctly. for (entity = [object entity]; entity != nil; entity = [entity superentity]) { NSEnumerator * propertyEnum = [[entity properties] objectEnumerator]; NSPropertyDescription * property; while ((property = [propertyEnum nextObject]) != nil) { [object removeObserver: observer forKeyPath: [property name]]; } } } } @interface GSMergePolicy : NSObject @end @implementation GSMergePolicy @end @interface NSManagedObjectContext (GSCoreDataInternal) /** * Sets up the properties of `object' according to the fetched * representation of them in `propertyValues' (see * Documentation/GSPersistentStore.txt on details on how this * dictionary is structured). The `mergeChanges' argument behaves * like the same name argument described in -[NSManagedObjectContext * refreshObject:mergeChanges:]. */ - (void) _setFetchedPropertyValues: (NSDictionary *) propertyValues ofObject: (NSManagedObject *) object mergeChanges: (BOOL) mergeChanges; /** * Sets the relationship described by `relationship' in `object' * to `value'. What's important to note is that `value' isn't the * actual destination object or a collection of destination objects, * but is instead the representation fetched from the persistent store * i.e. the destination object's ID or a collection of IDs. */ - (void) _setRelationship: (NSRelationshipDescription *) relationship fetchedValue: (id) value ofObject: (NSManagedObject *) object; /** * Registers the provided objects with the receiver, manipulating * their retain count to correctly fit our retaining behavior set * by -setRetainsRegisteredObjects:. */ - (void) _registerObjects: (NSSet *) objects; /** * Equivalent to -_registerObjects:, but operates on only one object. */ - (void) _registerObject: (NSManagedObject *) object; /** * Unregisters the provided objects with the receiver, manipulating * their retain count to correctly fit our retaining behavior set * by -setRetainsRegisteredObjects:. */ - (void) _unregisterObjects: (NSSet *) objects; /** * Equivalent to -_unregisterObjects:, but operates on only one object. */ - (void) _unregisterObject: (NSManagedObject *) object; @end /* * Implementation note: * Has anybody got any idea on why this class is supposed to conform to * NSCoding? Even if you've got one, then how to archive it when all of * it's instance variables don't support coding? Did somebody in Apple * sleep when designing this? */ @implementation NSManagedObjectContext + (void) initialize { if (NSErrorMergePolicy == nil) { /* NSErrorMergePolicy = [GSErrorMergePolicy new]; NSMergeByPropertyStoreTrumpMergePolicy = [GSMergeByPropertyStoreTrumpMergePolicy new]; NSMergeByPropertyObjectTrumpMergePolicy = [GSMergeByPropertyObjectTrumpMergePolicy new]; NSRollbackMergePolicy = [GSRollbackMergePolicy new]; NSOverwriteMergePolicy = [GSOverwriteMergePolicy new]; */ } } - (void) dealloc { TEST_RELEASE(_lock); TEST_RELEASE(_storeCoordinator); TEST_RELEASE(_registeredObjects); TEST_RELEASE(_insertedObjects); TEST_RELEASE(_updatedObjects); TEST_RELEASE(_deletedObjects); TEST_RELEASE(_undoManager); TEST_RELEASE(_mergePolicy); [super dealloc]; } - (id) init { if ((self = [super init])) { _lock = [NSRecursiveLock new]; _undoManager = [NSUndoManager new]; _registeredObjects = [NSMutableSet new]; _insertedObjects = [NSMutableSet new]; _updatedObjects = [NSMutableSet new]; _deletedObjects = [NSMutableSet new]; ASSIGN(_mergePolicy, NSErrorMergePolicy); } return self; } /** * Returns the persistent store coordinator associated with the receiver. */ - (NSPersistentStoreCoordinator *) persistentStoreCoordinator { return _storeCoordinator; } /** * Sets the persistent store coordinator of the receiver. A managed * object context isn't fully functional until it is connected to * a persistent store coordinator. * * @param coordinator The persistent store coordinator to use. */ - (void) setPersistentStoreCoordinator: (NSPersistentStoreCoordinator *) coordinator { ASSIGN(_storeCoordinator, coordinator); } /** * Returns the current undo manager of the receiver. */ - (NSUndoManager *) undoManager { return _undoManager; } /** * Sets a new undo manager in the receiver. * * @param aManager The undo manager to use. */ - (void) setUndoManager: (NSUndoManager *) aManager { ASSIGN(_undoManager, aManager); } /** * Sends `-undo' to the receiver's undo manager. */ - (void) undo { [_undoManager undo]; } /** * Sends `-redo' to the receiver's undo manager. */ - (void) redo { [_undoManager redo]; } // TODO - (void) rollback { NSEnumerator * e; NSManagedObject * object; // clear all actions from the undo manager [_undoManager removeAllActions]; // remove any inserted or deleted objects RemoveKVOSetupFromObjects(self, _insertedObjects); [_insertedObjects removeAllObjects]; RemoveKVOSetupFromObjects(self, _deletedObjects); [_deletedObjects removeAllObjects]; // and restore the state of all objects to their commited values e = [_registeredObjects objectEnumerator]; while ((object = [e nextObject]) != nil) { if ([object isFault] == NO) { NSDictionary * commitedValues = [object commitedValuesForKeys: nil]; NSEnumerator * commitedValuesEnumerator = [[commitedValues allKeys] objectEnumerator]; NSString * key; while ((key = [commitedValuesEnumerator nextObject]) != nil) { [object setPrimitiveValue: [commitedValues objectForKey: key] forKey: key]; } } } } /** * Resets the receiver. This in particular means: * * - all objects registered with the receiver are unregistered and released. * * - all actions recorded in the undo manager are removed. */ - (void) reset { /* From what I (Saso) understood, this method works as a shorthand. * To achieve the same effect, we could just as well recreate the * context anew and replace the old one, but this way we won't have * to swap them and aby additional settings (e.g. merge policy, * staleness interval, persistent store coordinator) will remain * untouched. */ // remove all objects [self _unregisterObjects: _registeredObjects]; [_insertedObjects removeAllObjects]; [_updatedObjects removeAllObjects]; [_deletedObjects removeAllObjects]; // reset the undo manager [_undoManager removeAllActions]; } /** * Saves all changed objects in the receiver to the persistent store. * * The save is carried out by first consulting the receiver's * merge policy to resolve potential conflicts between the version of * the objects in the receiver and the versions in the persistent store. * * @param errorPtr A pointer to a location which will be set to point to * an error object in case an error arises during saving. * * @return YES in case saving succeeds, NO if the operation fails. */ - (BOOL) save: (NSError **) errorPtr { /* * FIXME: why does Apple spec say that this method should abort * immediately in case of an error only when NULL is specified as * the `error' argument? (If non-NULL is passed, it should continue * and aggregate further errors inside the error object.) Should * a potentially destructive operation (and saving *is* a destructive * operation - it permanently overwrites data) not be aborted as soon * as a problem is detected? It is no problem extending this method * to just continue in case of an error and then return all errors as * an aggregate error, but that's very likely not something we want. */ NSEnumerator * e; NSManagedObject * object; NSMutableSet * objectsToSave; NSError * error = nil; if (_storeCoordinator == nil) { [NSException raise: NSInternalInconsistencyException format: _(@"-[NSManagedObjectContext save:]: Cannot save a managed " @"object context which isn't connected to a persistent store " @"coordinator.")]; } // assign unassigned objects to a persistent store e = [_insertedObjects objectEnumerator]; while ((object = [e nextObject]) != nil) { if ([[object objectID] isTemporaryID]) { GSPersistentStore * store = [_storeCoordinator persistentStoreContainingEntity: [object entity]]; if (store != nil) { [self assignObject: object toPersistentStore: store]; } else // no store contains the specified entity - cannot save object { NSDictionary * userInfo = [NSDictionary dictionaryWithObject: object forKey: NSAffectedObjectsErrorKey]; SetNonNullError(errorPtr, [NSError errorWithDomain: NSCoreDataErrorDomain code: NSPersistentStoreIncompatibleSchemaError userInfo: userInfo]); return NO; } } } // first, put in all changed objects objectsToSave = [[_updatedObjects mutableCopy] autorelease]; // then all objects which have newly been inserted (which may be // a subset of the previous ones) [objectsToSave unionSet: _insertedObjects]; // remove any objects from the list which are scheduled for deletion [objectsToSave minusSet: _deletedObjects]; // first delete the objects scheduled for deletion e = [_deletedObjects objectEnumerator]; while ((object = [e nextObject]) != nil) { [_storeCoordinator deleteObjectWithID: [object objectID]]; } // and finally merge the objects to be saved with the persistent // store coordinator e = [objectsToSave objectEnumerator]; while ((object = [e nextObject]) != nil) { if (![_mergePolicy mergeObject: object withStoreCoordinator: _storeCoordinator error: &error]) { SetNonNullError(errorPtr, error); return NO; } } if (![_storeCoordinator commitChangesError: &error]) { SetNonNullError(errorPtr, error); return NO; } // sync our internal state sets [self _unregisterObjects: _deletedObjects]; [_insertedObjects removeAllObjects]; [_updatedObjects removeAllObjects]; [_deletedObjects removeAllObjects]; return YES; } /** * Queries whether the receiver has some unsaved changes. Changes are * objects being changed, inserted or removed. * * @return YES if the receiver does have unsaved changes, NO otherwise. */ - (BOOL) hasChanges { return ([_updatedObjects count] > 0) || ([_insertedObjects count] > 0) || ([_deletedObjects count] > 0); } /** * Returns the object registered in the receiver with object ID `objectID', * or `nil' if an object of the specified ID isn't registered in the * receiver. */ - (NSManagedObject *) objectRegisteredForID: (NSManagedObjectID *) objectID { NSEnumerator * e; NSManagedObject * object; e = [_registeredObjects objectEnumerator]; while ((object = [e nextObject]) != nil) { if ([[object objectID] _isEqualToManagedObjectID: objectID] == YES) { return object; } } return nil; } /** * Attempts to locate an object registered in the receiver with * an object ID of `objectID'. If the object exists, it is returned. * If it doesn't, it is created as a fault and returned. The object * is assumed to exist in the associated persistent store. If it * doesn't, the time the fault is fired, an * NSInternalInconsistencyException is raised. */ - (NSManagedObject *) objectWithID: (NSManagedObjectID *) objectID { NSManagedObject * object; object = [self objectRegisteredForID: objectID]; // create the object as a fault if necessary if (object == nil) { NSManagedObject * object; object = [[[NSManagedObject alloc] _initAsFaultWithObjectID: objectID ownedByContext: self] autorelease]; [self _registerObject: object]; } return object; } /** * Retrieves all objects from the receiver and it's associated stores which * match the provided fetch request. N.B. objects are first looked for in * the receiver and only after that will the stores be consulted. That * means that any objects which were already brought into memory, even * those that have changed since then or have been altered in the persistent * store by another context, will be retrieved before any object from a * persistent store. Please also keep in mind the fact that the staleness * interval is again consulted by the store coordinator upon issuing a new * fetch to determine whether it can use cached data or has whether it has * to make a full round-trip to the persistent store(s). * * @param request The fetch request which to execute. * @param error A pointer to an error object where errors during fetching * of objects will be aggreggated. * * @return An array of objects which matched the fetch request's criteria. * If no objects matched, an empty array is returned. In case of an error, * `nil' is returned and the `error' argument is filled with a description * of the error which occured. */ - (NSArray *) executeFetchRequest: (NSFetchRequest *) request error: (NSError **) error { NSEntityDescription * entity = [request entity]; NSPredicate * predicate = [request predicate]; NSMutableArray * fetchedObjects; NSEnumerator * e; NSManagedObject * object; NSMutableSet * fetchedObjectsIDs; NSArray * storedObjects; fetchedObjects = [NSMutableArray array]; // fetch all matching objects from the context first e = [_registeredObjects objectEnumerator]; while ((object = [e nextObject]) != nil) { // ignore deleted objects if (ObjectMatchedByFetchRequest(object, request) && [_deletedObjects containsObject: object] == NO) { [fetchedObjects addObject: object]; } } // record the object IDs of already present objects fetchedObjectsIDs = [NSMutableSet setWithCapacity: [fetchedObjects count]]; e = [fetchedObjects objectEnumerator]; while ((object = [e nextObject]) != nil) { [fetchedObjectsIDs addObject: [object objectID]]; } // and tell the store to execute the fetch request, ignoring // already fetched object IDs storedObjects = [_storeCoordinator _executeFetchRequest: request ignoreIDs: fetchedObjectsIDs stalenessInterval: _stalenessInterval error: error]; if (storedObjects != nil) { [fetchedObjects addObjectsFromArray: storedObjects]; } // store error else { return nil; } // now, apply any sorting descriptors [fetchedObjects sortUsingDescriptors: [request sortDescriptors]]; return [[fetchedObjects copy] autorelease]; } /** * Inserts the `object' into the receiver. The next time the receiver * is saved it will be written into the persistent store. Upon inserting * the object, an NSManagedObjectContextObjectsDidChangeNotification * is posted to the default notification center with the * NSInsertedObjectsKey bound to a set containing the inserted object * in the user info dictionary. */ - (void) insertObject: (NSManagedObject *) object { NSDictionary * userInfo; // re-inserting a deleted object brings it in again if ([_deletedObjects containsObject: object] == YES) { [_deletedObjects removeObject: object]; } // otherwise if it isn't registered yet schedule it for addition else if ([_registeredObjects containsObject: object] == NO) { [self _registerObject: object]; [_insertedObjects addObject: object]; } else { return; } [object _insertedIntoContext: self]; [object _setDeleted: NO]; [_undoManager registerUndoWithTarget: self selector: @selector(deleteObject:) object: object]; userInfo = [NSDictionary dictionaryWithObject: [NSSet setWithObject: object] forKey: NSInsertedObjectsKey]; [[NSNotificationCenter defaultCenter] postNotificationName: NSManagedObjectContextObjectsDidChangeNotification object: self userInfo: userInfo]; } /** * Schedules the `object' for deletion from the persistent store of the * receiver. The next time the receiver is saved, the object will be * permanently removed from the persistent store. */ - (void) deleteObject: (NSManagedObject *) object { if ([_registeredObjects containsObject: object] == YES) { NSDictionary * userInfo; // we must do this first to make sure that removing the object // from our internal tables doesn't deallocate it [_undoManager registerUndoWithTarget: self selector: @selector(insertObject:) object: object]; // an unsaved object is removed immediately if ([_insertedObjects containsObject: object]) { [self _unregisterObject: object]; [_insertedObjects removeObject: object]; } // otherwise it is scheduled for deletion else { [_deletedObjects addObject: object]; } userInfo = [NSDictionary dictionaryWithObject: [NSSet setWithObject: object] forKey: NSDeletedObjectsKey]; [[NSNotificationCenter defaultCenter] postNotificationName: NSManagedObjectContextObjectsDidChangeNotification object: self userInfo: userInfo]; } } /** * Assigns an newly inserted object to be stored in a particular * persistent store. * * @param anObject The object to be assigned to the persitent store. * It must be an NSManagedObject which has been newly inserted into * the receiver - reassigning already saved objects isn't possible. * * @param aPersistentStore The persistent store to which to assign * the object. It must be a persistent store from the persistent * stores of the persistent store coordinator associated with the * receiver, otherwise an NSInvalidArgumentException is raised. */ - (void) assignObject: (id) anObject toPersistentStore: (id) aPersistentStore { // Why doesn't this method declare that `obj' must be a managed object?? NSManagedObject * object = anObject; GSPersistentStore * store = aPersistentStore; NSManagedObjectID * oldObjectID, * newObjectID; if (![object isKindOfClass: [NSManagedObject class]]) { [NSException raise: NSInvalidArgumentException format: _(@"-[NSManagedObjectContext assignObject:toPersistentStore:]: " @"Non-managed-object passed.")]; } if (_storeCoordinator == nil) { [NSException raise: NSInternalInconsistencyException format: _(@"-[NSManagedObjectContext assignObject:toPersistentStore:]: " @"Cannot assign an object to a store in a context that isn't " @"connected to a persistent store coordinator.")]; } if (![[_storeCoordinator persistentStores] containsObject: store]) { [NSException raise: NSInvalidArgumentException format: _(@"-[NSManagedObjectContext assignObject:toPersistentStore:]: " @"Cannot assign an object to a store which isn't in the " @"persistent store with which the context in which the object " @"lives is associated.")]; } /* * NB. We don't check whether the object has a temporary or a permanent * object ID, only whether it has already been commited to a persistent * store. Thus a newly inserted object can be reassigned several times * to different persistent stores before it is actually saved. The save * will commit it to the latest of the specified stores. After that, * however, one cannot change it's location anymore. */ if ([_insertedObjects containsObject: object] == NO) { [NSException raise: NSInvalidArgumentException format: _(@"-[NSManagedObjectContext assignObject:toPersistentStore:]: " @"Cannot assign an object to a persistent store which hasn't " @"been inserted.")]; } oldObjectID = [object objectID]; // construct a new object ID in which we will explicitly denote the // persistent store to which the object belongs newObjectID = [[[NSManagedObjectID alloc] _initWithEntity: [oldObjectID entity] persistentStore: store value: [store nextFreeIDValue]] autorelease]; [object _setObjectID: newObjectID]; } /** * Refreshes an object's persistent properties with data from the * persistent store, possibly overwriting changed values. In case * the object's data don't exist in the persistent store, an * NSInvalidArgumentException is raised. In case the persistent store * coordinator has cached data for this object (and the staleness) * interval hasn't been exceeded, no new fetch will be issued. * * @param object The object to be refreshed. * * @param mergeChanges If this argument is YES, then after all persistent * properties of the object have been sync'ed with the values in the * persistent store, any previously changed values of the object are * re-applied over the refreshed object. Also, any transient properties * are left untouched. If this argument is NO, then any changes made * to the object aren't re-applied and transient properties are released. */ - (void) refreshObject: (NSManagedObject *) object mergeChanges: (BOOL) mergeChanges { NSManagedObjectID * objectID = [object objectID]; NSDictionary * propertyValues; propertyValues = [_storeCoordinator fetchObjectWithID: [object objectID] fetchProperties: nil cacheStalenessInterval: _stalenessInterval]; if (propertyValues == nil) { [NSException raise: NSInvalidArgumentException format: _(@"-[NSManagedObjectContext refreshObject:mergeChanges:]: " @"Cannot refresh object - data for object doesn't exist in " @"the persistent store.")]; } [self _setFetchedPropertyValues: propertyValues ofObject: object mergeChanges: mergeChanges]; } /** * Returns a set of object which will be inserted into the persistent store * at the next save operation. */ - (NSSet *) insertedObjects { return [[_insertedObjects copy] autorelease]; } /** * Returns a set of object which have changed since the last successful * save operation. */ - (NSSet *) updatedObjects { return [[_updatedObjects copy] autorelease]; } /** * Returns a set of object which will be removed from the persistent store * at the next save operation. */ - (NSSet *) deletedObjects { return [[_deletedObjects copy] autorelease]; } /** * Returns a set containing all objects registered with the receiver. */ - (NSSet *) registeredObjects { return [[_registeredObjects copy] autorelease]; } /** * Attempts to lock the receiver, blocking if the lock is already taken. * * @see [NSManagedObjectContext unlock] * @see [NSManagedOBjectContext tryLock] */ - (void) lock { [_lock lock]; } /** * Unlocks the receiver. * * @see [NSManagedObjectContext lock] * @see [NSManagedOBjectContext tryLock] */ - (void) unlock { [_lock unlock]; } /** * Attempts to acquire the lock, but never blocks, even if the lock is * already taken. * * @return YES if the lock succeeds and NO if the lock is already taken * and locking it fails. * * @see [NSManagedObjectContext lock] * @see [NSManagedOBjectContext unlock] */ - (BOOL) tryLock { return [_lock tryLock]; } /** * Returns whether the receiver retains objects which are registered with it. * * @return YES if the receiver retains registered objects, NO otherwise. * * By default a managed object context does not retain it's * registered objects. */ - (BOOL) retainsRegisteredObjects { return _retainsRegisteredObjects; } /** * Sets whether the receiver retains objects which are registered with it * or not. By default, a managed object context does not retain * it's registered objects. * * Object's scheduled for addition, deletion or have changed are always * retained. */ - (void) setRetainsRegisteredObjects: (BOOL) flag { if (_retainsRegisteredObjects != flag) { _retainsRegisteredObjects = flag; // retain them if (_retainsRegisteredObjects == YES) { [_registeredObjects makeObjectsPerformSelector: @selector(retain)]; } // release them else { [_registeredObjects makeObjectsPerformSelector: @selector(release)]; } } } /** * Returns the staleness interval of the receiver. * * @see [NSManagedObjectContext setStalenessInterval:] */ - (NSTimeInterval) stalenessInterval { return _stalenessInterval; } /** * Sets the staleness interval of the receiver. The staleness interval * determines when fetches are being done whether any cached data is * reused or a new fetch is issued. * * @param timeInterval The new staleness interval. Passing zero means * infinite staleness interval. */ - (void) setStalenessInterval: (NSTimeInterval) timeInterval { _stalenessInterval = timeInterval; } /** * Returns the merge policy of the receiver. * * @see [NSManagedObjectContext setMergePolicy:] */ - (id) mergePolicy { return _mergePolicy; } /** * Sets the merge policy of the receiver. The merge policy defines how * conflicts during save operations are handled. The default policy of * newly created managed object contexts is NSErrorMergePolicy. * * @param policy Identifies the merge policy to use. The value must be * one of: * * - NSErrorMergePolicy * - NSMergeByPropertyStoreTrumpMergePolicy * - NSMergeByPropertyObjectTrumpMergePolicy * - NSOverwriteMergePolicy * - NSRollbackMergePolicy *. * As an extension, GNUstep Core Data allows you to pass even your custom * subclass of GSMergePolicy, since all merge policies are implemented * as subclasses of it. */ - (void) setMergePolicy: (id) policy { if (![policy isKindOfClass: [GSMergePolicy class]]) { [NSException raise: NSInvalidArgumentException format: _(@"-[NSManagedObjectContext setMergePolicy:]: " @"Invalid merge policy (%@) specified."), policy]; } ASSIGN(_mergePolicy, policy); } @end /** * Private methods of GNUstep Core Data for NSManagedObjectContext. * Do NOT invoke these from external code! */ @implementation NSManagedObjectContext (GSCoreDataPrivate) /** * Invoked by a managed object registered with the receiver when an * attribute or to-one relationship changes. This method records the * change with the receiver's undo manager. * * @param object The object in which the change occured. * @param key The key of which the value changed. * @param oldValue The old value of the key. * @param newValue The new value of the key. */ - (void) _object: (NSManagedObject *) object changedValueForSingleKey: (NSString *) key oldValue: (id) oldValue newValue: (id) newValue { [[_undoManager prepareWithInvocationTarget: object] setValue: oldValue forKey: key]; } /** * Invoked by a managed object registered with the receiver when an * a to-many relationship changes. This method records the change * with the receiver's undo manager. * * @param object The object in which the change occured. * @param key The key of which the value changed. * @param oldValue The old contents of the to-many relationship as a set. * @param mutationKind The kind of change on the to-many relationship. * @param objects The objects with which the change has been performed. */ - (void) _object: (NSManagedObject *) object changedValueForMultiKey: (NSString *) key oldValue: (NSSet *) oldValue setMutation: (NSKeyValueSetMutationKind) mutationKind usingObjects: (NSSet *) objects { NSMutableSet * newValue = [object mutableSetValueForKey: key]; id um = [_undoManager prepareWithInvocationTarget: newValue]; switch (mutationKind) { case NSKeyValueUnionSetMutation: { // FIXED by hns to make it compile - but not checked!!! NSMutableSet *ms = [[objects mutableCopy] autorelease]; [ms minusSet: oldValue]; [um minusSet: ms]; break; } case NSKeyValueMinusSetMutation: { // FIXED by hns to make it compile - but not checked!!! NSMutableSet *ms = [[objects mutableCopy] autorelease]; [ms intersectSet: oldValue]; [um unionSet: ms]; break; } case NSKeyValueIntersectSetMutation: [um unionSet: oldValue]; break; case NSKeyValueSetSetMutation: [um setSet: oldValue]; break; } } @end /** * Internal methods methods of GNUstep Core Data for NSManagedObjectContext. * Do NOT invoke these from external code! */ @implementation NSManagedObjectContext (GSCoreDataInternal) - (void) _setFetchedPropertyValues: (NSDictionary *) newPropertyValues ofObject: (NSManagedObject *) object mergeChanges: (BOOL) mergeChanges { Class relationshipClass = [NSRelationshipDescription class]; NSEntityDescription * entity; NSDictionary * changedValues; NSEnumerator * e; NSString * key; if (mergeChanges == YES) { changedValues = [object changedValues]; } // process all properties, traversing the entity inheritance hierarchy for (entity = [object entity]; entity != nil; entity = [entity superentity]) { NSEnumerator * e = [[entity properties] objectEnumerator]; NSPropertyDescription * property; while ((property = [e nextObject]) != nil) { NSString * key = [property name]; id newValue = [newPropertyValues objectForKey: key]; if ([property isTransient]) { if (mergeChanges == NO) { // flush transient values if merging isn't requested [object setValue: nil forKey: key]; } } else { if (newValue == nil) { [object setValue: nil forKey: key]; } else if ([property isKindOfClass: relationshipClass]) { [self _setRelationship: (NSRelationshipDescription*) property fetchedValue: newValue ofObject: object]; } else { [object setValue: newValue forKey: key]; } } } } [object _flushChangedValues]; if (mergeChanges == YES) { // now set back all properties which have changed e = [[changedValues allKeys] objectEnumerator]; while ((key = [e nextObject]) != nil) { [object setValue: [changedValues objectForKey: key] forKey: key]; } } } - (void) _setRelationship: (NSRelationshipDescription *) relationship fetchedValue: (id) value ofObject: (NSManagedObject *) object { NSString * key = [relationship name]; NSManagedObjectID * destinationID; NSManagedObject * destinationObject; // If a relationship is a to-many relationship, the value will be a // collection containing a set of managed object IDs of the destination // objects. Extract them, get the objects and set them as the value // of the key. if ([relationship isToMany]) { NSMutableSet * newRelationshipValue; NSEnumerator * e; NSAssert1([value isKindOfClass: [NSSet class]] || [value isKindOfClass: [NSArray class]], _(@"Encountered non-collection value (%@) from store when setting " @"a to-many relationship."), value); newRelationshipValue = [NSMutableSet setWithCapacity: [value count]]; e = [value objectEnumerator]; while ((destinationID = [e nextObject]) != nil) { destinationObject = [self objectWithID: destinationID]; [newRelationshipValue addObject: destinationObject]; } [object setValue: newRelationshipValue forKey: key]; } // Otherwise the value is a single managed object ID the destination // object. Get the object and set it. else { NSAssert1([value isKindOfClass: [NSManagedObjectID class]], _(@"Encountered non-object-ID value (%@) from store when setting " @"a relationship."), value); destinationID = value; destinationObject = [self objectWithID: destinationID]; [object setValue: destinationObject forKey: key]; } } - (void) _registerObjects: (NSSet *) objects { if (_retainsRegisteredObjects == NO) { NSMutableSet * tmp; tmp = [[objects mutableCopy] autorelease]; // cut out only the objects which aren't registered yet [tmp minusSet: _registeredObjects]; // first put them in the set, then release [_registeredObjects unionSet: objects]; [tmp makeObjectsPerformSelector: @selector(release)]; } else { [_registeredObjects unionSet: objects]; } } - (void) _registerObject: (NSManagedObject *) object { if (_retainsRegisteredObjects == NO) { // we need to check whether it's already registered to not // confuse the retain/release machinery by releasing it more // times if ([_registeredObjects containsObject: object] == NO) { [_registeredObjects addObject: object]; [object release]; } } else { [_registeredObjects addObject: object]; } } - (void) _unregisterObjects: (NSSet *) objects { if (_retainsRegisteredObjects == NO) { NSMutableSet * tmp; tmp = [[objects mutableCopy] autorelease]; // cut out only registered objects [tmp intersectSet: _registeredObjects]; // first retain, then remove from set [tmp makeObjectsPerformSelector: @selector(retain)]; [_registeredObjects minusSet: tmp]; } else { [_registeredObjects unionSet: objects]; } } - (void) _unregisterObject: (NSManagedObject *) object { if (_retainsRegisteredObjects) { if ([_registeredObjects containsObject: object] == YES) { // first retain, then remove from set [object retain]; [_registeredObjects removeObject: object]; } } else { [_registeredObjects removeObject: object]; } } @end NSString * const NSManagedObjectContextObjectsDidChangeNotification = @"NSManagedObjectContextObjectsDidChangeNotification"; NSString * const NSManagedObjectContextDidSaveNotification = @"NSManagedObjectContextDidSaveNotification"; NSString * const NSInsertedObjectsKey = @"NSInsertedObjectsKey"; NSString * const NSUpdatedObjectsKey = @"NSUpdatedObjectsKey"; NSString * const NSDeletedObjectsKey = @"NSDeletedObjectsKey";