/**
EOEditingContext.m
EOEditingContext Class
Copyright (C) 2000-2002 Free Software Foundation, Inc.
Date: June 2000
$Revision$
$Date$
This file is part of the GNUstep Database Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**/
#include "config.h"
RCS_ID("$Id$")
//TODO EOMultiReaderLocks
#ifndef GNUSTEP
#include
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
@class EOEntityClassDescription;
/*
* These EOAccess specific declarations are intended to
* supress long compiler warnings. Non the less we should
* avoid dependancies on EOAccess.
*/
@interface NSObject(EOEntityWarningSupression)
- (NSString *) name;
- (EOGlobalID *) globalIDForRow:(NSDictionary *)row;
@end
@interface EOEntityClassDescription : EOClassDescription
- (NSObject *) entity;
@end
@interface EOEditingContext(EOEditingContextPrivate)
- (void) incrementUndoTransactionID;
- (BOOL) handleError: (NSException *)exception;
- (BOOL) handleErrors: (NSArray *)exceptions;
- (void) _enqueueEndOfEventNotification;
- (void) _sendOrEnqueueNotification: (NSNotification *)notification
selector: (SEL)selector;
- (void) _insertObject: (id)object
withGlobalID: (EOGlobalID *)gid;
- (void) _processObjectStoreChanges: (NSDictionary *)changes;
- (void) _processDeletedObjects;
- (void) _processOwnedObjectsUsingChangeTable: (NSHashTable*)changeTable
deleteTable: (NSHashTable*)deleteTable;
- (void)_processNotificationQueue;
- (void) _observeUndoManagerNotifications;
- (void) _registerClearStateWithUndoManager;
- (void) _forgetObjectWithGlobalID:(EOGlobalID *)gid;
- (void) _invalidateObject:(id)obj withGlobalID: (EOGlobalID *)gid;
- (void) _invalidateObjectsWithGlobalIDs: (NSArray*)gids;
- (NSDictionary *) _objectBasedChangeInfoForGIDInfo: (NSDictionary *)changes;
- (NSMutableSet *)_mutableSetFromToManyArray: (NSArray *)array;
- (NSArray *)_uncommittedChangesForObject: (id)obj
fromSnapshot: (NSDictionary *)snapshot;
- (NSArray *)_changesFromInvalidatingObjectsWithGlobalIDs: (NSArray *)globalIDs;
- (void) _resetAllChanges;
@end
@interface EOThreadSafeQueue : NSObject
{
GSLazyRecursiveLock *lock;
NSMutableArray *arr;
}
-(void)addItem:(id)object;
-(id)removeItem;
@end
@implementation EOThreadSafeQueue
- (id)init
{
if ((self=[super init]))
{
lock = [GSLazyRecursiveLock new];
arr = [NSMutableArray new];
}
return self;
}
- (void)dealloc
{
RELEASE(lock);
RELEASE(arr);
[super dealloc];
}
-(void)addItem:(id)object
{
NSParameterAssert(object);
[lock lock];
[arr addObject: object];
[lock unlock];
}
-(id)removeItem
{
id item = nil;
[lock lock];
if ([arr count])
{
item = [arr objectAtIndex: 0];
[arr removeObjectAtIndex: 0];
}
[lock unlock];
return item;
}
@end
@implementation EOEditingContext
static EONull *null = nil;
static Class EOEditingContextClass = nil;
static Class EOAssociationClass = nil;
static EOObjectStore *defaultParentStore = nil;
static NSTimeInterval defaultFetchLag = 3600.0;
static NSHashTable *ecDeallocHT = 0;
static NSHashTable *assocDeallocHT = 0;
/* Notifications */
NSString *EOObjectsChangedInEditingContextNotification
= @"EOObjectsChangedInEditingContextNotification";
NSString *EOEditingContextDidSaveChangesNotification
= @"EOEditingContextDidSaveChangesNotification";
/* Local constants */
NSString *EOConstObject = @"EOConstObject";
NSString *EOConstChanges = @"EOConstChanges";
NSString *EOConstKey = @"EOConstKey";
NSString *EOConstValue = @"EOConstValue";
NSString *EOConstAdd = @"EOConstAdd";
NSString *EOConstDel = @"EOConstDel";
/*
* This function is used during change processing to multiple changes
* to one class property.
* Either value or the add and remove arrays should be set.
* The arrays may be emtpy but they must be supplied unless
* the value is supplied.
* The value is set via the takeStoredValue:forKey: method (i.e. avoiding
* the formal accessor machinery) replacing any EONull's with nil.
* When add and remove arrays are given, processing starts with the remove
* array and then continues with the add array.
*/
static inline void
_mergeValueForKey(id obj, id value,
NSArray *add, NSArray *del,
NSString *key)
{
id relObj;
unsigned int i,n;
NSCAssert(((value == nil && add != nil && del !=nil)
|| (value != nil && add == nil && del == nil)),
@"Illegal usage of function.");
for (i = 0, n = [del count]; i < n; i++)
{
relObj = [del objectAtIndex: i];
[obj removeObject: relObj fromPropertyWithKey: key];
}
for (i = 0, n = [add count]; i < n; i++)
{
relObj = [add objectAtIndex: i];
[obj addObject: relObj toPropertyWithKey: key];
}
if (add == nil && del == nil)
{
value = (value == null) ? nil : value;
[obj takeStoredValue: value forKey: key];
}
}
+ (void)initialize
{
if (self == [EOEditingContext class] && defaultParentStore == nil)
{
defaultParentStore = [EOObjectStoreCoordinator defaultCoordinator];
null = [EONull null];
EOEditingContextClass = self;
EOAssociationClass = NSClassFromString(@"EOAssociation");
}
}
+ (void)objectDeallocated:(id)object
{
[[object editingContext] forgetObject: object];
}
+ (NSTimeInterval)defaultFetchTimestampLag
{
return defaultFetchLag;
}
+ (void)setDefaultFetchTimestampLag: (NSTimeInterval)lag
{
defaultFetchLag = lag;
}
+ (void)setInstancesRetainRegisteredObjects: (BOOL)flag
{
[self notImplemented: _cmd];
}
+ (BOOL)instancesRetainRegisteredObjects
{
[self notImplemented: _cmd];
return NO;
}
- (void) _eoNowMultiThreaded: (NSNotification *)notification
{
//TODO
}
- (id) initWithParentObjectStore: (EOObjectStore *)parentObjectStore
{
//OK
if ((self = [super init]))
{
_flags.propagatesDeletesAtEndOfEvent = YES; //Default behavior
ASSIGN(_objectStore, [EOEditingContext defaultParentObjectStore]); //parentObjectStore instead of defaultParentObjectStore ?
_unprocessedChanges = NSCreateHashTable(NSObjectHashCallBacks, 32);
_unprocessedDeletes = NSCreateHashTable(NSObjectHashCallBacks, 32);
_unprocessedInserts = NSCreateHashTable(NSObjectHashCallBacks, 32);
_insertedObjects = NSCreateHashTable(NSObjectHashCallBacks, 32);
_deletedObjects = NSCreateHashTable(NSObjectHashCallBacks, 32);
_changedObjects = NSCreateHashTable(NSObjectHashCallBacks, 32);
/* We may not retain the objects we are managing. */
_globalIDsByObject = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSObjectMapValueCallBacks,
32);
_objectsByGID = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSNonOwnedPointerMapValueCallBacks,
32);
_snapshotsByGID = [[NSMutableDictionary alloc] initWithCapacity:16];
_eventSnapshotsByGID = [[NSMutableDictionary alloc] initWithCapacity:16];
_editors = [NSMutableArray new];
_lock = [NSRecursiveLock new];
_undoManager = [EOUndoManager new];
[self _observeUndoManagerNotifications];
/*
[self setStopsValidationAfterFirstError:YES];
[self setPropagatesDeletesAtEndOfEvent:YES];
*/
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_objectsChangedInStore:)
name: EOObjectsChangedInStoreNotification
object: _objectStore];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_invalidatedAllObjectsInStore:)
name: EOInvalidatedAllObjectsInStoreNotification
object: _objectStore];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_globalIDChanged:)
name: EOGlobalIDChangedNotification
object: nil];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(_eoNowMultiThreaded:)
name: NSWillBecomeMultiThreadedNotification
object: nil];
/*
[self setStopsValidationAfterFirstError:NO];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(_objectsChangedInSubStore:)
name:EOObjectsChangedInStoreNotification
object:nil];
*/
}
return self;
}
- (id) init
{
EOObjectStore *defaultStore = [EOEditingContext defaultParentObjectStore];
return [self initWithParentObjectStore: defaultStore];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
DESTROY(_objectStore);
DESTROY(_undoManager);
NSFreeHashTable(_unprocessedChanges);
NSFreeHashTable(_unprocessedDeletes);
NSFreeHashTable(_unprocessedInserts);
NSFreeHashTable(_insertedObjects);
NSFreeHashTable(_deletedObjects);
NSFreeHashTable(_changedObjects);
NSFreeMapTable(_globalIDsByObject);
NSFreeMapTable(_objectsByGID);
DESTROY(_snapshotsByGID);
DESTROY(_eventSnapshotsByGID);
DESTROY(_editors);
DESTROY(_lock);
[super dealloc];
}
/*
* This method processes CHANGES which should be an array of
* dictionaries with EOConstKey and either EOConstValue or
* both EOConstAdd and EOConstDel entries and merges the changes
* into OBJ.
*/
- (void) _mergeObject: (id)obj withChanges: (NSArray *)changes
{
unsigned int i,n;
NSString *key;
NSArray *add, *del;
NSDictionary *change;
id val;
for(i = 0, n = [changes count]; i < n; i++)
{
change = [changes objectAtIndex: i];
key = [change objectForKey: EOConstKey];
val = [change objectForKey: EOConstValue];
if (val == nil)
{
add = [change objectForKey: EOConstAdd];
del = [change objectForKey: EOConstDel];
NSAssert(add!=nil && del!=nil,@"Invalid changes dictionary.");
}
_mergeValueForKey(obj, val, add, del, key);
}
}
/*
* This method creates an dictionary of changed objects by the
* change action, based on a similar dictionary with globalIDs.
* For each key of EODeletedKey, EOInsertedKey, EOInvalidatedKey
* and EOUpdatedKey an array of corresponding GIDs from the
* CHANGES array will be mapped to the cooresponding objects
* managed by the receiver for ther returned dictionary.
*/
- (NSDictionary *)_objectBasedChangeInfoForGIDInfo: (NSDictionary *)changes
{
NSString *keys[] = { EODeletedKey,
EOInsertedKey,
EOInvalidatedKey,
EOUpdatedKey };
NSArray *valueArray[4];
NSDictionary *dict;
int i;
EOFLOGObjectFnStart();
for (i=0; i<4; i++)
{
NSArray *gids = [changes objectForKey: keys[i]];
unsigned cnt = [gids count];
id values[cnt>GS_MAX_OBJECTS_FROM_STACK?0:cnt];
id *valuesPStart;
id *valuesP;
unsigned j;
valuesPStart = valuesP = (cnt > GS_MAX_OBJECTS_FROM_STACK
? GSAutoreleasedBuffer(sizeof(id) * cnt)
: values);
for (j=0; jtmpGID mapping. */
NSMapInsert(_globalIDsByObject, object, gid);
NSMapRemove(_objectsByGID, tempGID);
NSMapInsert(_objectsByGID, gid, object);
}
else
{
// object is from other editingcontext
EOFLOGObjectLevelArgs(@"EOEditingContextValues",
@"nothing done: object with gid '%@' "
@"is from other editing context",
tempGID);
}
snapshot = [_snapshotsByGID objectForKey: tempGID];
EOFLOGObjectLevelArgs(@"EOEditingContext",
@"_snapshotsByGID snapshot=%@", snapshot);
if (snapshot)
{
[_snapshotsByGID removeObjectForKey: tempGID];
[_snapshotsByGID setObject: snapshot
forKey: gid];
}
else if (object)
{
/* What should happen? The object is maintained by
this editing context but it doesn't have a snapshot!
Should we creat it? */
/*
// set snapshot with last committed values
EOFLOGObjectLevelArgs(@"EOEditingContextValues",
@"adding new object = %@",
[self objectForGlobalID:gid]);
EOFLOGObjectLevelArgs(@"EOEditingContextValues",
@"object class = %@",
NSStringFromClass([object class]));
EOFLOGObjectLevelArgs(@"EOEditingContextValues",
@"with snapshot = %@",
[[self objectForGlobalID:gid] snapshot]);
[_snapshotsByGID setObject:[[self objectForGlobalID:gid] snapshot]
forKey:gid];
*/
}
else
{
// object is from other editingcontext
EOFLOGObjectLevelArgs(@"EOEditingContextValues",
@"nothing done: object with gid '%@' "
@"is from other editing context",
tempGID);
}
snapshot = [_eventSnapshotsByGID objectForKey: tempGID];
EOFLOGObjectLevelArgs(@"EOEditingContext",
@"_eventSnapshotsByGID snapshot=%@", snapshot);
if (snapshot)
{
[_eventSnapshotsByGID removeObjectForKey: tempGID];
[_eventSnapshotsByGID setObject: snapshot
forKey: gid];
}
}
EOFLOGObjectFnStop();
}
- (void)_processNotificationQueue
{
EOThreadSafeQueue *queue = _notificationQueue;
NSDictionary *dict, *userInfo;
NSString *name;
SEL selector;
if ([self tryLock])
{
while ((dict = [queue removeItem]))
{
name = [dict objectForKey: @"selector"];
selector = NSSelectorFromString(name);
userInfo = [dict objectForKey: @"userInfo"];
[self performSelector: selector
withObject: userInfo];
}
[self unlock];
}
else
{
/* Setup new call to _processNotificationQueue */
}
}
- (void)_sendOrEnqueueNotification: (NSNotification *)notification
selector: (SEL)selector
{
if ([self tryLock])
{
[self _processNotificationQueue];
[self performSelector: selector
withObject: [notification userInfo]];
[self unlock];
}
else
{
static NSDictionary *emptyDict = nil;
NSDictionary *userInfo = nil;
NSDictionary *queDict;
if (emptyDict == nil)
{
emptyDict = [NSDictionary new];
}
userInfo = [notification userInfo];
if (userInfo == nil)
{
userInfo = emptyDict;
}
queDict = [NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromSelector(selector), @"selector",
userInfo, @"userInfo",
nil ];
[(EOThreadSafeQueue *)_notificationQueue addItem: queDict];
}
}
//"Invalidate All Objects"
- (void) invalidateAllObjects
{
NSArray *gids;
[self _resetAllChanges];
/* The reference implementation seems to first obtain the objects
via -registeredObjects and then calls globalIDForObject: on each
but this seems wasteful and we have the array at easy access. */
gids = NSAllMapTableKeys(_objectsByGID);
[_objectStore invalidateObjectsWithGlobalIDs: gids];
[[NSNotificationCenter defaultCenter] postNotificationName: EOInvalidatedAllObjectsInStoreNotification
object: self
userInfo: nil];
}
//"Receive ???? notification"
- (void) _invalidatedAllObjectsInStore: (NSNotification*)notification
{
NSEmitTODO();
[self notImplemented: _cmd]; //TODO
}
- (void) _forgetObjectWithGlobalID:(EOGlobalID*)gid
{
id object = [self objectForGlobalID: gid];
if (object != nil)
{
[self forgetObject: object];
NSHashRemove(_insertedObjects, object);
NSHashRemove(_deletedObjects, object);
NSHashRemove(_changedObjects, object);
if ([EOFault isFault: object] == NO)
{
[object clearProperties];
}
}
}
- (void) _invalidateObject: (id)object
withGlobalID: (EOGlobalID*)gid
{
SEL sel = @selector(editingContext:shouldInvalidateObject:globalID:);
BOOL invalidate = YES;
if ([_delegate respondsToSelector: sel])
{
invalidate = [_delegate editingContext: self
shouldInvalidateObject: object
globalID: gid];
}
if (invalidate == YES)
{
[self refaultObject: object
withGlobalID: gid
editingContext: self];
}
}
- (void) _invalidateObjectWithGlobalID: (EOGlobalID*)gid
{
id object = [self objectForGlobalID: gid];
if ((object != nil ) && ([EOFault isFault: object] == NO))
{
[self _invalidateObject: object withGlobalID: gid];
}
}
- (void) _invalidateObjectsWithGlobalIDs: (NSArray*)gids
{
unsigned i, count;
SEL oaiSEL = @selector(objectAtIndex:);
SEL iowgidSEL = @selector(_invalidateObjectWithGlobalID:);
IMP oaiIMP = [gids methodForSelector: oaiSEL];
IMP iowgidIMP = [self methodForSelector: iowgidSEL];
EOFLOGObjectFnStart();
for (i=0, count=[gids count]; i
NSEmitTODO();
//TODO [self notImplemented:_cmd]; //TODO
}
}
}
enumerator = NSEnumerateHashTable(objectsToInsert);
while ((object = (id)NSNextHashEnumeratorItem(&enumerator)))
{
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Insert %@", object);
[self insertObject: object];
}
NSFreeHashTable(objectsToInsert);
//TODO-NOW: use deleteTable !
//[self notImplemented:_cmd]; //TODO
EOFLOGObjectFnStop();
}
- (void) registerUndoForModifiedObject: (id)object
{
EOGlobalID *gid;
NSDictionary *snapshot;
NSDictionary *undoObject;
EOFLOGObjectFnStart();
EOFLOGObjectLevelArgs(@"EOEditingContext", @"object=%@", object);
gid = [self globalIDForObject: object];
EOFLOGObjectLevelArgs(@"EOEditingContext", @"gid=%@", gid);
snapshot = [self currentEventSnapshotForObject: object];
undoObject = [NSDictionary dictionaryWithObjectsAndKeys:
object, @"object",
snapshot, @"snapshot",
nil, nil];
[_undoManager registerUndoWithTarget: self
selector: @selector(_undoUpdate:)
object: undoObject];
[_eventSnapshotsByGID removeObjectForKey: gid];
EOFLOGObjectFnStop();
}
- (void) _undoUpdate: (id)param0
{
NSEmitTODO();
//TODO
// receive a dict with object and snapshot ? (cf registerUndoForModifiedObject:)
}
- (void)incrementUndoTransactionID
{
_undoTransactionID++;
_flags.registeredUndoTransactionID = NO;
}
-(void)setLevelsOfUndo:(int)levels
{
//TODO
};
- (void) _registerClearStateWithUndoManager
{
//pas appellee dans le cas d'un delete ?
id object;
EOFLOGObjectFnStart();
object = [NSNumber numberWithUnsignedInt: _undoTransactionID];
_flags.registeredUndoTransactionID = YES;
EOFLOGObjectLevelArgs(@"EOEditingContext", @"_undoManager=%p", _undoManager);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"_undoManager=%@", _undoManager);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"self=%@", self);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"object=%@", object);
[_undoManager registerUndoWithTarget: self
selector: @selector(_clearChangedThisTransaction:)
object: object];
EOFLOGObjectFnStop();
}
- (void)_clearChangedThisTransaction:(NSNumber *)transID
{
EOFLOGObjectFnStart();
if (_undoTransactionID == [transID unsignedShortValue])
{
static NSDictionary *info = nil;
if (info == nil)
{
NSArray *arr = [NSArray array];
info = [[NSDictionary alloc] initWithObjectsAndKeys:
arr, EOInsertedKey,
arr, EODeletedKey,
arr, EOUpdatedKey,
nil];
}
[self processRecentChanges];
NSResetHashTable(_changedObjects);
[self incrementUndoTransactionID];
[[NSNotificationCenter defaultCenter]
postNotificationName: EOObjectsChangedInEditingContextNotification
object: self
userInfo: info];
}
EOFLOGObjectFnStop();
}
- (void)deleteObject: (id)object
{
EOFLOGObjectFnStart();
EOFLOGObjectLevelArgs(@"EOEditingContext", @"object=%@", object);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Unprocessed: %@",
[self unprocessedDescription]);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Objects: %@",
[self objectsDescription]);
if (!NSHashGet(_unprocessedDeletes, object)
&& !NSHashGet(_deletedObjects, object))
{
NSMethodSignature *undoMethodSignature = nil;
NSUndoManager *undoManager;
//EOGlobalID *gid = [self globalIDForObject: object];
[self _registerClearStateWithUndoManager];
undoManager = (NSUndoManager*)[self undoManager];
[undoManager prepareWithInvocationTarget: self];
undoMethodSignature = [undoManager methodSignatureForSelector:
@selector(_insertObject:withGlobalID:)];
/*
//TODO
if base class of undoManager ret nil, undomanager call editingcont methodSignatureForSelector: _insertObject:withGlobalID:
undoManager forwardInvocation:
_NSUndoInvocation avec selector:_insertObject:withGlobalID:
target self (editing context)
[undoManager _registerUndoObject:arget: EOEditingContext -- selector:_insertObject:withGlobalID:
_invocation=NSInvocation * object: Description:
next=_NSUndoObject * object:0x0 Description:*nil*
previous=_NSUndoObject * object:0x0 Description:*nil*
_target=id object: Description:
[self _prepareEventGrouping];
*/
NSHashInsert(_unprocessedDeletes, object);
[self _enqueueEndOfEventNotification];
}
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Unprocessed: %@",
[self unprocessedDescription]);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Objects: %@",
[self objectsDescription]);
EOFLOGObjectFnStop();
}
- (void)lockObject: (id)object
{
EOGlobalID *gid = [self globalIDForObject: object];
if (gid == nil)
[NSException raise: NSInvalidArgumentException
format: @"%@ -- %@ 0x%x: globalID for object 0x%x not found",
NSStringFromSelector(_cmd),
NSStringFromClass([self class]),
self,
object];
[self lockObjectWithGlobalID: gid
editingContext: self];
}
// Saving
- (BOOL)hasChanges
{
if(NSCountHashTable(_insertedObjects)
|| NSCountHashTable(_deletedObjects)
|| NSCountHashTable(_changedObjects)
|| NSCountHashTable(_unprocessedInserts)
|| NSCountHashTable(_unprocessedDeletes)
|| NSCountHashTable(_unprocessedChanges))
return YES;
return NO;
}
- (void)didSaveChanges
{
NSHashTable *hashTables[3]={ _insertedObjects,
_deletedObjects,//??
_changedObjects };
NSMutableArray *objectsForNotification[3]={ [NSMutableArray array],//inserted
[NSMutableArray array],//deleted
[NSMutableArray array] };//updated
NSEnumerator *enumerator = nil;
id object = nil;
int which;
EOFLOGObjectFnStart();
EOFLOGObjectLevelArgs(@"EOEditingContext",
@"Changes nb: inserted:%d deleted:%d changed:%d",
NSCountHashTable(_insertedObjects),
NSCountHashTable(_deletedObjects),
NSCountHashTable(_changedObjects));
_flags.ignoreChangeNotification=NO; //MG??
for (which = 0; which < 3; which++)
{
NSHashEnumerator hashEnumerator = NSEnumerateHashTable(hashTables[which]);
while ((object = (id)NSNextHashEnumeratorItem(&hashEnumerator)))
{
[objectsForNotification[which] addObject: object];
[self clearOriginalSnapshotForObject: object]; //OK for update
}
}
enumerator = [NSAllHashTableObjects(_deletedObjects) objectEnumerator];
while ((object = [enumerator nextObject]))
{
[self forgetObject: object];
[object clearProperties];
}
NSResetHashTable(_insertedObjects);
NSResetHashTable(_deletedObjects);
NSResetHashTable(_changedObjects);
[self incrementUndoTransactionID]; //OK for update
{
EOGlobalID *gid;
enumerator = [[_snapshotsByGID allKeys] objectEnumerator];
while ((gid = [enumerator nextObject]))
{
[_snapshotsByGID setObject: [[self objectForGlobalID:gid] snapshot]
forKey: gid];
}
}
[[NSNotificationCenter defaultCenter]
postNotificationName: @"EOEditingContextDidSaveChangesNotification"
object: self
userInfo: [NSDictionary dictionaryWithObjectsAndKeys:
objectsForNotification[0], EOInsertedKey,
objectsForNotification[1], EODeletedKey,
objectsForNotification[2], EOUpdatedKey,
nil, nil]];
EOFLOGObjectFnStop();
}
- (void)saveChanges
{
id object = nil;
NSEnumerator *enumerator;
EOFLOGObjectFnStart();
//TODOLOCK
[self lock];
NS_DURING
{
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Unprocessed: %@",
[self unprocessedDescription]);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Objects: %@",
[self objectsDescription]);
enumerator = [_editors objectEnumerator];
while ((object = [enumerator nextObject]))
[object editingContextWillSaveChanges: self];
if (_delegateRespondsTo.willSaveChanges)
[_delegate editingContextWillSaveChanges: self];
[self _processRecentChanges]; // filled lists _changedObjects, _deletedObjects, _insertedObjects, breaks relations
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Unprocessed: %@",
[self unprocessedDescription]);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Objects: %@",
[self objectsDescription]);
_flags.registeredForCallback = NO;
[self validateChangesForSave]; // may raise exception
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Unprocessed: %@",
[self unprocessedDescription]);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Objects: %@",
[self objectsDescription]);
//?? [EOObserverCenter notifyObserversObjectWillChange: nil];
_flags.ignoreChangeNotification = YES;
EOFLOGObjectLevel(@"EOEditingContext",
@"_objectStore saveChangesInEditingContext");
[_objectStore saveChangesInEditingContext: self];
EOFLOGObjectLevel(@"EOEditingContext", @"self didSaveChanges");
[self didSaveChanges];
EOFLOGObjectFnStop();
}
NS_HANDLER
{
NSLog(@"%@ (%@)", localException, [localException reason]);
NSDebugMLog(@"%@ (%@)", localException, [localException reason]);
[self unlock];
[localException raise];
}
NS_ENDHANDLER;
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Unprocessed: %@",
[self unprocessedDescription]);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Objects: %@",
[self objectsDescription]);
[self unlock];
}
- (NSException *) tryToSaveChanges
{
NSException *newException = nil;
EOFLOGObjectFnStartOrCond(@"EOEditingContext");
NS_DURING
{
[self saveChanges];
}
NS_HANDLER
{
if(_messageHandler
&& [_messageHandler
respondsToSelector: @selector(editingContext:presentErrorMessage:)] == YES)
[_messageHandler editingContext: self
presentErrorMessage: [localException reason]];
newException = localException;
}
NS_ENDHANDLER;
EOFLOGObjectFnStopOrCond(@"EOEditingContext");
return newException;
}
- (void)revert
{
NSEnumerator *enumerator;
EOGlobalID *gid;
enumerator = [_eventSnapshotsByGID keyEnumerator];
while ((gid = [enumerator nextObject]))
{
[[self objectForGlobalID: gid]
updateFromSnapshot: [_eventSnapshotsByGID objectForKey: gid]];
}
[_undoManager removeAllActions];
[_undoManager beginUndoGrouping];
NSResetHashTable(_unprocessedChanges);
NSResetHashTable(_unprocessedDeletes);
NSResetHashTable(_unprocessedInserts);
NSResetHashTable(_changedObjects);
NSResetHashTable(_deletedObjects);
NSResetHashTable(_insertedObjects);
}
- (void) clearOriginalSnapshotForObject: (id)object
{
//Consider OK
EOGlobalID *gid = nil;
EOFLOGObjectFnStart();
gid = [self globalIDForObject: object];
if (gid)
{
[_snapshotsByGID removeObjectForKey: gid];
}
EOFLOGObjectFnStop();
}
- (id)objectForGlobalID:(EOGlobalID *)globalID
{
id object;
EOFLOGObjectFnStart();
EOFLOGObjectLevelArgs(@"EOEditingContext", @"gid=%@", globalID);
// NSDebugMLLog(@"XXX",@"_objectsByGID=%@",NSAllMapTableKeys(_objectsByGID));
object = NSMapGet(_objectsByGID, globalID);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"object=%p", object);
EOFLOGObjectFnStop();
return object;
}
- (EOGlobalID *)globalIDForObject: (id)object
{
//Consider OK
EOGlobalID *gid;
/*
[self recordForObject:object]
[self notImplemented:_cmd]; //TODO
*/
EOFLOGObjectFnStart();
EOFLOGObjectLevelArgs(@"EOEditingContext",
@"ed context=%p _globalIDsByObject=%p object=%p",
self, _globalIDsByObject, object);
// EOFLOGObjectLevelArgs(@"EOEditingContext",@"_globalIDsByObject Values=%@",NSAllMapTableValues(_globalIDsByObject));
gid = NSMapGet(_globalIDsByObject, object);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"gid=%@", gid);
EOFLOGObjectFnStop();
return gid;
}
- (NSHashTable*) recordForGID: (EOGlobalID *)globalID
{
[self notImplemented: _cmd]; //TODO
return NULL;
}
- (NSHashTable*) recordForObject: (id)object
{
[self notImplemented: _cmd]; //TODO
return NULL;
}
- (void)setDelegate: (id)delegate
{
_delegate = delegate;
_delegateRespondsTo.willRunLoginPanel =
[delegate respondsToSelector: @selector(databaseContext:willRunLoginPanelToOpenDatabaseChannel:)];
_delegateRespondsTo.shouldFetchObjects =
[delegate respondsToSelector: @selector(editingContext:shouldFetchObjectsDescribedByFetchSpecification:)];
_delegateRespondsTo.shouldInvalidateObject =
[delegate respondsToSelector: @selector(editingContext:shouldInvalidateObject:globalID:)];
_delegateRespondsTo.shouldMergeChanges =
[delegate respondsToSelector: @selector(editingContext:shouldMergeChangesForObject:)];
_delegateRespondsTo.shouldPresentException =
[delegate respondsToSelector: @selector(editingContext:shouldPresentException:)];
_delegateRespondsTo.shouldUndoUserActions =
[delegate respondsToSelector: @selector(editingContextShouldUndoUserActionsAfterFailure:)];
_delegateRespondsTo.shouldValidateChanges =
[delegate respondsToSelector: @selector(editingContextShouldValidateChanges:)];
_delegateRespondsTo.willSaveChanges =
[delegate respondsToSelector: @selector(editingContextWillSaveChanges:)];
}
- (id)delegate
{
return _delegate;
}
- (EOObjectStore *)parentObjectStore
{
return _objectStore;
}
- (EOObjectStore *)rootObjectStore
{
EOObjectStore *rootObjectStore;
EOFLOGObjectFnStart();
if ([_objectStore isKindOfClass: [EOEditingContext class]] == YES)
rootObjectStore = [(EOEditingContext *)_objectStore rootObjectStore];
else
rootObjectStore=_objectStore;
EOFLOGObjectFnStop();
return rootObjectStore;
}
// Advanced methods //////////////////////////
- (void)setUndoManager: (NSUndoManager *)undoManager
{
ASSIGN(_undoManager, undoManager);
}
- (NSUndoManager *)undoManager
{
return _undoManager;
}
- (void)_revertChange: (NSDictionary *)dict
{
[[dict objectForKey: @"object"]
updateFromSnapshot: [dict objectForKey: @"snapshot"]];
}
- (void)objectWillChange: (id)object
{
EOFLOGObjectFnStart();
// EOFLOGObjectLevelArgs(@"EOEditingContext", @"object=%@", object);
EOFLOGObjectLevelArgs(@"EOEditingContext",
@"object=%@ _flags.ignoreChangeNotification=%d",
object, (int)_flags.ignoreChangeNotification);
if (_flags.ignoreChangeNotification == NO)
{
NSDictionary *snapshot;
EOFLOGObjectLevelArgs(@"EOEditingContext", @"*** object change %p %@",
object, [object class]);
//recordForObject:
snapshot = [object snapshot]; // OK
EOFLOGObjectLevelArgs(@"EOEditingContext", @"snapshot=%@", snapshot);
//if not in _unprocessedChanges: add in snaps and call _enqueueEndOfEventNotification
/*
[_undoManager registerUndoWithTarget:self
selector:@selector(noop:)
object:nil;];
*/
////////
if (NSHashInsertIfAbsent(_unprocessedChanges, object)) //The object is already here
{
EOFLOGObjectLevel(@"EOEditingContext",
@"_enqueueEndOfEventNotification");
[self _enqueueEndOfEventNotification];
if(_undoManager)
[_undoManager registerUndoWithTarget: self
selector:@selector(_revertChange:)
object:[NSDictionary dictionaryWithObjectsAndKeys:
object, @"object",
[object snapshot], @"snapshot",
nil]];
}
else
{
//???????????
EOGlobalID *gid = [self globalIDForObject: object];
EOFLOGObjectLevel(@"EOEditingContext",
@"insert into xxsnapshotsByGID");
[_eventSnapshotsByGID setObject: [object snapshot]
forKey: gid];
[_snapshotsByGID setObject: [object snapshot]
forKey: gid];
if (_flags.autoLocking == YES)
[self lockObject: object];
[self _enqueueEndOfEventNotification];
}
}
EOFLOGObjectFnStop();
}
- (void)recordObject: (id)object
globalID: (EOGlobalID *)globalID
{
//OK
EOFLOGObjectFnStart();
EOFLOGObjectLevelArgs(@"EOEditingContext",
@"Record %p for %@ in ed context %p _globalIDsByObject=%p",
object, globalID, self, _globalIDsByObject);
NSAssert(object, @"No Object");
NSAssert(globalID, @"No GlobalID");
/* Global hash table for faster dealloc. */
if (!ecDeallocHT)
{
ecDeallocHT
= NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 64);
}
NSHashInsert(ecDeallocHT, object);
EOFLOGObjectLevel(@"EOEditingContext", @"insertInto _globalIDsByObject");
NSMapInsert(_globalIDsByObject, object, globalID);
//TODO: delete
{
id aGID2;
id aGID = NSMapGet(_globalIDsByObject, object);
NSAssert1(aGID, @"Object %p recorded but can't retrieve it directly !",
object);
aGID2 = [self globalIDForObject: object];
NSAssert1(aGID2, @"Object %p recorded but can't retrieve it with globalIDForObject: !", object);
}
EOFLOGObjectLevel(@"EOEditingContext", @"insertInto _objectsByGID");
NSMapInsert(_objectsByGID, globalID, object);
EOFLOGObjectLevel(@"EOEditingContext", @"addObserver");
[EOObserverCenter addObserver: self
forObject: object];
//call EOAccessFaultHandler targetClass
EOFLOGObjectFnStop();
}
- (void)forgetObject: (id)object
{
EOGlobalID *gid;
/* Global hash table for faster dealloc. */
NSHashRemove(ecDeallocHT, object);
gid = [self globalIDForObject: object];
NSMapRemove(_globalIDsByObject, object);
NSMapRemove(_objectsByGID, gid);
[EOObserverCenter removeObserver: self
forObject: object];
}
- (NSArray *)registeredObjects
{
return NSAllMapTableValues(_objectsByGID);
}
- (NSArray *)updatedObjects
{
NSMutableArray *updatedObjects = [NSMutableArray array];
NSHashEnumerator changedEnum = NSEnumerateHashTable(_changedObjects);
id object;
while ((object = (id)NSNextHashEnumeratorItem(&changedEnum)))
{
if (!NSHashGet(_deletedObjects, (const void*)object))
[updatedObjects addObject: object];
}
// NSEndHashTableEnumeration(changedEnum);
return updatedObjects;
}
- (NSArray *)insertedObjects
{
return NSAllHashTableObjects(_insertedObjects);
//TODO: remove inserted objectss which are deleted ???
}
- (NSArray *)deletedObjects
{
return NSAllHashTableObjects(_deletedObjects);
}
- (void)_revertInsert: (id)object
{
NSHashRemove(_insertedObjects, object);
}
- (void)_revertUpdate: (id)object
{
NSHashRemove(_changedObjects, object);
}
- (void)_revertDelete: (id)object
{
NSHashRemove(_deletedObjects, object);
}
// ***************************
// *
// * Objects for DELETE
// *
// * compares object from _unprocessedChanges
// * between their state from _eventSnapshots and [object snapshot]
// *
// * you can see if any other instead of this EditingContext has changed the object
// *
// * decision of keeping or deleting these changed objects (RelationshipObjects)
// *
// ****************************
- (void)processRecentChanges
{
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Unprocessed: %@",
[self unprocessedDescription]);
EOFLOGObjectLevelArgs(@"EOEditingContext", @"Objects: %@",
[self objectsDescription]);
[self _processRecentChanges];
}
- (BOOL)propagatesDeletesAtEndOfEvent
{
return _flags.propagatesDeletesAtEndOfEvent;
}
- (void)setPropagatesDeletesAtEndOfEvent: (BOOL)propagatesDeletesAtEndOfEvent
{
_flags.propagatesDeletesAtEndOfEvent = propagatesDeletesAtEndOfEvent;
}
- (BOOL)stopsValidationAfterFirstError
{
return !_flags.exhaustiveValidation;
}
- (void)setStopsValidationAfterFirstError:(BOOL)flag
{
_flags.exhaustiveValidation = !flag;
}
- (BOOL)locksObjectsBeforeFirstModification
{
return _flags.autoLocking;
}
- (void)setLocksObjectsBeforeFirstModification: (BOOL)flag
{
_flags.autoLocking = flag;
}
// Snapshotting
/** Returns a dictionary containing a snapshot of object that
reflects its committed values (last values putted in the
database; i.e. values before changes were made on the object).
It is updated after commiting new values.
**/
- (NSDictionary *)committedSnapshotForObject: (id)object
{
//OK
return [_snapshotsByGID objectForKey: [self globalIDForObject: object]];
}
/** Returns a dictionary containing a snapshot of object with
its state as it was at the beginning of the current event loop.
After the end of the current event, upon invocation of
processRecentChanges, the snapshot is updated to hold the
modified state of the object.**/
- (NSDictionary *)currentEventSnapshotForObject: (id)object
{
//OK
return [_eventSnapshotsByGID objectForKey: [self globalIDForObject: object]];
}
- (NSDictionary *)uncommittedChangesForObject: (id)object
{
return [object changesFromSnapshot:
[self currentEventSnapshotForObject: object]];
}
- (void)refaultObjects
{
NSMutableArray *objs = [[NSMutableArray new] autorelease];
NSEnumerator *objsEnum;
id obj;
[self processRecentChanges];
[objs addObjectsFromArray: NSAllMapTableKeys(_globalIDsByObject)];
[objs removeObjectsInArray: [self insertedObjects]];
[objs removeObjectsInArray: [self deletedObjects]];
[objs removeObjectsInArray: [self updatedObjects]];
objsEnum = [objs objectEnumerator];
while ((obj = [objsEnum nextObject]))
[self refaultObject: obj
withGlobalID: [self globalIDForObject: obj]
editingContext: self];
}
// Refaults all objects that haven't been modified, inserted or deleted.
- (void)setInvalidatesObjectsWhenFreed: (BOOL)flag
{
_flags.skipInvalidateOnDealloc = flag;
}
- (BOOL)invalidatesObjectsWhenFreed
{
return _flags.skipInvalidateOnDealloc; // TODO contrario ??
}
- (void)addEditor: (id)editor
{
[_editors addObject: editor];
}
- (void)removeEditor: (id)editor
{
[_editors removeObject: editor];
}
- (NSArray *)editors
{
return _editors;
}
- (void)setMessageHandler: (id)handler
{
_messageHandler = handler;
}
- (id)messageHandler
{
return _messageHandler;
}
- (id)faultForGlobalID: (EOGlobalID *)globalID
editingContext: (EOEditingContext *)context
{
//OK
id object = [self objectForGlobalID: globalID];
if (!object)
{
BOOL isTemporary = [globalID isTemporary];
if (isTemporary)
{
NSEmitTODO();
[self notImplemented: _cmd]; //TODO
}
else
{
object = [_objectStore faultForGlobalID: globalID
editingContext: self];
}
}
return object;
}
- (id)faultForRawRow: (NSDictionary *)row
entityNamed: (NSString *)entityName
editingContext: (EOEditingContext *)context
{
EOEntityClassDescription *classDesc;
EOGlobalID *globalID;
id object;
id objectCopy;
classDesc = (id)[EOClassDescription classDescriptionForEntityName:
entityName];
#warning (stephane@sente.ch) ERROR: trying to use EOEntity/EOEntityDescription which are defined in EOAccess
globalID = [[classDesc entity] globalIDForRow: row];
object = [self objectForGlobalID: globalID];
if (object)
{
if (context == self)
return object;
objectCopy = [classDesc createInstanceWithEditingContext: context
globalID: globalID
zone: NULL];
NSAssert1(objectCopy, @"No Object. classDesc=%@", classDesc);
[objectCopy updateFromSnapshot: [object snapshot]];
[context recordObject: objectCopy
globalID: globalID];
return objectCopy;
}
object = [_objectStore faultForRawRow: row
entityNamed: entityName
editingContext: self];
return object;
}
- (id)faultForRawRow: (id)row entityNamed: (NSString *)entityName
{
id object;
EOFLOGObjectFnStartOrCond(@"EOEditingContext");
object = [self faultForRawRow: row
entityNamed: entityName
editingContext: self];
EOFLOGObjectFnStopOrCond(@"EOEditingContext");
return object;
}
- (NSArray *)arrayFaultWithSourceGlobalID: (EOGlobalID *)globalID
relationshipName: (NSString *)name
editingContext: (EOEditingContext *)context
{
NSArray *fault;
id object = [self objectForGlobalID: globalID];
id objectCopy;
if (object)
{
if (context == self)
{
fault = [object valueForKey:name];
if (fault)
return fault;
}
else
{
objectCopy = [[EOClassDescription classDescriptionForEntityName:
[globalID entityName]]
createInstanceWithEditingContext: context
globalID: globalID
zone: NULL];
NSAssert1(objectCopy, @"No Object. globalID=%@", globalID);
[objectCopy updateFromSnapshot: [object snapshot]];
[context recordObject: objectCopy
globalID: globalID];
return [objectCopy valueForKey: name];
}
}
return [_objectStore arrayFaultWithSourceGlobalID: globalID
relationshipName: name
editingContext: self];
}
- (void)initializeObject: (id)object
withGlobalID: (EOGlobalID *)globalID
editingContext: (EOEditingContext *)context
{
//near OK
EOObjectStore *objectStore = nil;
_flags.ignoreChangeNotification = YES;
if (self == context)
{
objectStore = [(EOObjectStoreCoordinator*)_objectStore objectStoreForGlobalID: globalID];
[objectStore initializeObject: object
withGlobalID: globalID
editingContext: context];
}
else
{
NSEmitTODO();
[self notImplemented: _cmd];//TODO
}
// [EOObserverCenter notifyObserversObjectWillChange: nil];
_flags.ignoreChangeNotification = NO;
}
- (NSArray *)objectsForSourceGlobalID: (EOGlobalID *)globalID
relationshipName: (NSString *)name
editingContext: (EOEditingContext *)context
{
NSArray *objects = nil;
if (self == context)
{
//TODOLOCK
[self lock];
NS_DURING
{
objects = [_objectStore objectsForSourceGlobalID: globalID
relationshipName: name
editingContext: context];
}
NS_HANDLER
{
NSLog(@"%@ (%@)", localException, [localException reason]);
NSDebugMLog(@"%@ (%@)", localException, [localException reason]);
[self unlock];
[localException raise];
}
NS_ENDHANDLER;
[self unlock];
}
else
{
NSEmitTODO();
[self notImplemented: _cmd];//TODO
}
return objects;
}
- (void)refaultObject: object
withGlobalID: (EOGlobalID *)globalID
editingContext: (EOEditingContext *)context
{
//Near OK
if (object)
{
//call globalID isTemporary //ret NO
if (self == context)//??
{
//NO: in EODatabaseConetxt [object clearProperties];
//OK
[_objectStore refaultObject: object
withGlobalID: globalID
editingContext: context];
//OK
[self clearOriginalSnapshotForObject: object];
}
else
{
[self notImplemented: _cmd];
}
}
}
- (void)saveChangesInEditingContext: (EOEditingContext *)context
{
if (context != self)
{
NSArray *objects;
NSEnumerator *objsEnum;
EOGlobalID *gid;
id object, localObject;
objects = [context insertedObjects];
objsEnum = [objects objectEnumerator];
while ((object = [objsEnum nextObject]))
{
gid = [context globalIDForObject: object];
localObject = [[EOClassDescription classDescriptionForEntityName:
[gid entityName]]
createInstanceWithEditingContext: context
globalID: gid
zone: NULL];
NSAssert1(localObject, @"No Object. gid=%@", gid);
[localObject updateFromSnapshot: [object snapshot]];
[self recordObject: localObject
globalID: gid];
}
objects = [context updatedObjects];
objsEnum = [objects objectEnumerator];
while ((object = [objsEnum nextObject]))
{
gid = [context globalIDForObject: object];
localObject = [self objectForGlobalID: gid];
[localObject updateFromSnapshot:[object snapshot]];
}
objects = [context deletedObjects];
objsEnum = [objects objectEnumerator];
while ((object = [objsEnum nextObject]))
{
gid = [context globalIDForObject: object];
localObject = [self objectForGlobalID: gid];
[self deleteObject: localObject];
}
}
}
- (NSArray *)objectsWithFetchSpecification: (EOFetchSpecification *)fetch
editingContext: (EOEditingContext *)context
{
//OK
NSArray *objects = nil;
EOFLOGObjectFnStart();
EOFLOGObjectLevelArgs(@"EOEditingContext",
@"_objectStore=%@ fetch=%@ context=%@",
_objectStore, fetch, context);
[self lock]; //TODOLOCK
NS_DURING
{
objects = [_objectStore objectsWithFetchSpecification: fetch
editingContext: context];
}
NS_HANDLER
{
EOFLOGObjectLevelArgs(@"EOEditingContext", @"EXCEPTION: %@",
localException);
[self unlock]; //TODOLOCK
if ([self handleError: localException])
{
NSEmitTODO();
[self notImplemented: _cmd]; //TODO
}
else
{
NSEmitTODO();
[self notImplemented: _cmd]; //TODO
}
}
NS_ENDHANDLER;
[self unlock]; //TODOLOCK
EOFLOGObjectFnStop();
return objects;
}
- (void)lockObjectWithGlobalID: (EOGlobalID *)gid
editingContext: (EOEditingContext *)context
{
[_objectStore lockObjectWithGlobalID: gid
editingContext: context];
}
- (BOOL)isObjectLockedWithGlobalID: (EOGlobalID *)gid
editingContext: (EOEditingContext *)context
{
return [_objectStore isObjectLockedWithGlobalID: gid
editingContext: context];
}
@end
@implementation NSObject (EOEditingContext)
- (EOEditingContext *)editingContext
{
return [EOObserverCenter observerForObject: self
ofClass: [EOEditingContext class]];
}
@end
@implementation NSObject (EOEditors)
/** Called by the EOEditingContext to determine if the editor is "dirty" **/
- (BOOL)editorHasChangesForEditingContext: (EOEditingContext *)editingContext
{
return NO;
}
- (void)editingContextWillSaveChanges: (EOEditingContext *)editingContext
{
return;
}
@end
//
// EOMessageHandler informal protocol
//
@implementation NSObject (EOMessageHandlers)
- (void)editingContext: (EOEditingContext *)editingContext
presentErrorMessage: (NSString *)message
{
NSDebugMLog(@"error=%@", message);
}
- (BOOL)editingContext: (EOEditingContext *)editingContext
shouldContinueFetchingWithCurrentObjectCount: (unsigned)count
originalLimit: (unsigned)limit
objectStore: (EOObjectStore *)objectStore
{
return NO;
}
@end
@implementation EOEditingContext (EORendezvous)
+ (void)setSubstitutionEditingContext: (EOEditingContext *)ec
{
[self notImplemented: _cmd]; //TODO
}
+ (EOEditingContext *)substitutionEditingContext
{
return [self notImplemented: _cmd]; //TODO
}
+ (void)setDefaultParentObjectStore: (EOObjectStore *)store
{
ASSIGN(defaultParentStore, store);
}
+ (EOObjectStore *)defaultParentObjectStore
{
return defaultParentStore;
}
@end
@implementation EOEditingContext (EOStateArchiving)
+ (void)setUsesContextRelativeEncoding: (BOOL)yn
{
[self notImplemented: _cmd]; //TODO
}
+ (BOOL)usesContextRelativeEncoding
{
[self notImplemented: _cmd]; //TODO
return NO;
}
+ (void)encodeObject: (id)object
withCoder: (NSCoder *)coder
{
[self notImplemented: _cmd]; //TODO
}
+ (id)initObject: (id)object
withCoder: (NSCoder *)coder
{
return [self notImplemented: _cmd]; //TODO
}
@end
// Target action methods for InterfaceBuilder
//
@implementation EOEditingContext (EOTargetAction)
- (void)saveChanges: (id)sender // TODO
{
NS_DURING
[self saveChanges];
NS_HANDLER
{
if(_messageHandler
&& [_messageHandler
respondsToSelector: @selector(editingContext:presentErrorMessage:)] == YES)
[_messageHandler editingContext: self
presentErrorMessage: [localException reason]];
}
NS_ENDHANDLER;
}
- (void)refault: (id)sender
{
[self refaultObjects];
}
- (void)revert: (id)sender
{
[self revert];
}
- (void)refetch: (id)sender
{
[self invalidateAllObjects];
}
- (void)undo: (id)sender
{
[_undoManager undo];
}
- (void)redo: (id)sender
{
[_undoManager redo];
}
- (NSString*)unprocessedDescription
{
NSString *desc;
EOFLOGObjectFnStart();
desc = [NSString stringWithFormat: @"<%p:\nunprocessedChanges [nb:%d]=%p %@\n\nunprocessedDeletes [nb:%d]=%p %@\n\nunprocessedInserts[nb:%d]=%p %@>\n",
self,
NSCountHashTable(_unprocessedChanges),
_unprocessedChanges,
NSStringFromHashTable(_unprocessedChanges),
NSCountHashTable(_unprocessedDeletes),
_unprocessedDeletes,
NSStringFromHashTable(_unprocessedDeletes),
NSCountHashTable(_unprocessedInserts),
_unprocessedInserts,
NSStringFromHashTable(_unprocessedInserts)];
EOFLOGObjectFnStop();
return desc;
}
- (NSString*)objectsDescription
{
NSString *desc;
EOFLOGObjectFnStart();
desc = [NSString stringWithFormat: @"<%p:\nchangedObjects [nb:%d]=%p %@\n\ndeletedObjects [nb:%d]=%p %@\n\ninsertedObjects [nb:%d]=%p %@>\n",
self,
NSCountHashTable(_changedObjects),
_changedObjects,
NSStringFromHashTable(_changedObjects),
NSCountHashTable(_deletedObjects),
_deletedObjects,
NSStringFromHashTable(_deletedObjects),
NSCountHashTable(_insertedObjects),
_insertedObjects,
NSStringFromHashTable(_insertedObjects)];
EOFLOGObjectFnStop();
return desc;
}
@end
// To support multithreaded operation
@implementation EOEditingContext(EOMultiThreaded)
+ (void)setEOFMultiThreadedEnabled: (BOOL)flag
{
[self notImplemented: _cmd];
}
- (BOOL)tryLock
{
BOOL tryLock = NO;
EOFLOGObjectFnStart();
tryLock = [_lock tryLock];
if (tryLock)
{
_lockCount++;
}
EOFLOGObjectFnStop();
return tryLock;
}
- (void)lock
{
EOFLOGObjectFnStart();
[_lock lock];
_lockCount++;
EOFLOGObjectFnStop();
}
- (void)unlock
{
EOFLOGObjectFnStart();
_lockCount--;
[_lock unlock];
EOFLOGObjectFnStop();
}
- (void) _assertSafeMultiThreadedAccess: (SEL)param0
{
[self notImplemented: _cmd]; //TODO
}
@end
// Informations
@implementation EOEditingContext(EOEditingContextInfo)
- (NSDictionary*)unprocessedInfo
{
NSDictionary *infoDict = nil;
NSHashTable *hashTables[3] = { _unprocessedChanges,
_unprocessedDeletes,
_unprocessedInserts };
NSMutableArray *objectsForInfo[3] = { [NSMutableArray array], //inserted
[NSMutableArray array], //deleted
[NSMutableArray array] }; //updated
id object;
int which;
EOFLOGObjectFnStart();
for (which = 0; which < 3; which++)
{
NSHashEnumerator hashEnumerator = NSEnumerateHashTable(hashTables[which]);
while ((object = (id)NSNextHashEnumeratorItem(&hashEnumerator)))
{
NSString *info = [NSString stringWithFormat: @"%@ (%p)",
[[object entity] name],
object];
[objectsForInfo[which] addObject: info];
}
}
infoDict = [NSDictionary dictionaryWithObjectsAndKeys:
objectsForInfo[0], @"update",
objectsForInfo[1], @"delete",
objectsForInfo[2], @"insert",
nil, nil];
NSDebugMLog(@"infoDict=%@", infoDict);
EOFLOGObjectFnStop();
return infoDict;
}
- (NSDictionary*)pendingInfo
{
NSDictionary *infoDict = nil;
NSHashTable *hashTables[3] = { _insertedObjects,
_deletedObjects,
_changedObjects };
NSMutableArray *objectsForInfo[3] = { [NSMutableArray array], //inserted
[NSMutableArray array], //deleted
[NSMutableArray array] }; //updated
id object;
int which;
EOFLOGObjectFnStart();
for (which = 0; which < 3; which++)
{
NSHashEnumerator hashEnumerator = NSEnumerateHashTable(hashTables[which]);
while ((object = (id)NSNextHashEnumeratorItem(&hashEnumerator)))
{
NSString *info = [NSString stringWithFormat: @"%@ (%p)",
[[object entity] name],
object];
[objectsForInfo[which] addObject: info];
}
}
infoDict = [NSDictionary dictionaryWithObjectsAndKeys:
objectsForInfo[0], EOInsertedKey,
objectsForInfo[1], EODeletedKey,
objectsForInfo[2], EOUpdatedKey,
nil, nil];
NSDebugMLog(@"infoDict=%@", infoDict);
EOFLOGObjectFnStop();
return infoDict;
}
@end
@implementation NSObject (DeallocHack)
/*
* This is a real hack that shows that the design of this
* library did not take the reference counting mechanisms
* of OpenStep to heart. I'm sorry kids, but this seems
* how it has to be done to remain compatible. Any hints
* on how to speed this up are appreciated. But understand
* that we don't know the classes which need to call this
* and there could be deep hierarchy.
*/
- (void) dealloc
{
if (ecDeallocHT && NSHashGet(ecDeallocHT, self))
{
[EOEditingContextClass objectDeallocated: self];
}
if (assocDeallocHT && NSHashGet(assocDeallocHT, self))
{
[EOAssociationClass objectDeallocated: self];
}
NSDeallocateObject (self);
}
@end