/**
EOClassDescription.m
EOClassDescription Class
Copyright (C) 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
Author: Mirko Viviani
Date: February 2000
Author: Manuel Guesdon
Date: November 2001
$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$")
#ifndef NeXT_Foundation_LIBRARY
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#else
#include
#endif
#ifndef GNUSTEP
#include
#endif
#include
#include
#include
#include
#include
#include
#include
// NOTE: (stephane@sente.ch) Should we subclass NSClassDescription?
/*
d.ayers@inode.at: Yes, once we wish to support code written for
for EOF > WO4.5. No, for now because we don't have direct access
to the NSMapTable of base/Foundation so we would loose efficiency
and gain no real benefit.
*/
@interface NSObject (SupressCompilerWarnings)
+(id)defaultGroup;
@end
@implementation EOClassDescription
NSString *EOClassDescriptionNeededNotification
= @"EOClassDescriptionNeededNotification";
NSString *EOClassDescriptionNeededForClassNotification
= @"EOClassDescriptionNeededForClassNotification";
NSString *EOClassDescriptionNeededForEntityNameNotification
= @"EOClassDescriptionNeededForEntityNameNotification";
NSString *EOValidationException = @"EOValidationException";
NSString *EOAdditionalExceptionsKey = @"EOAdditionalExceptionsKey";
NSString *EOValidatedObjectUserInfoKey = @"EOValidatedObjectUserInfoKey";
NSString *EOValidatedPropertyUserInfoKey = @"EOValidatedPropertyUserInfoKey";
/*
* Globals
*/
static NSMapTable *classDescriptionForEntity = NULL;
static NSMapTable *classDescriptionForClass = NULL;
static id classDelegate = nil;
+ (void)initialize
{
if (self == [EOClassDescription class])
{
Class cls = NSClassFromString(@"EOModelGroup");
classDescriptionForClass = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSObjectMapValueCallBacks,
32);
classDescriptionForEntity = NSCreateMapTable(NSObjectMapKeyCallBacks,
NSObjectMapValueCallBacks,
32);
if (cls != Nil)
[cls defaultGroup]; // Insure correct initialization.
}
}
/*
* Methods
*/
+ (id)classDelegate
{
id delegate;
NSLock *lock = GDL2GlobalLock();
if (lock == nil)
{
delegate = classDelegate;
}
else
{
[lock lock];
delegate = classDelegate;
if (delegate != nil)
{
AUTORELEASE(RETAIN(delegate));
}
[lock unlock];
}
return delegate;
}
+ (EOClassDescription *)classDescriptionForClass:(Class)aClass
{
EOClassDescription *classDescription;
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb", @"aClass=%@", aClass);
NSAssert(aClass, @"No class");
NSDebugMLLog(@"gsdb", @"class name=%s", GSNameFromClass(aClass));
classDescription = NSMapGet(classDescriptionForClass, aClass);
NSDebugMLLog(@"gsdb", @"classDescription=%@", classDescription);
if (!classDescription)
{
[[NSNotificationCenter defaultCenter]
postNotificationName: EOClassDescriptionNeededForClassNotification
object: aClass];
classDescription = NSMapGet(classDescriptionForClass, aClass);
NSDebugMLLog(@"gsdb", @"classDescription=%@", classDescription);
if (!classDescription)
{
NSLog(@"Warning: No class description for class named: %s",
GSNameFromClass(aClass));
}
}
EOFLOGObjectFnStop();
return classDescription;
}
+ (EOClassDescription *)classDescriptionForEntityName: (NSString *)entityName
{
EOClassDescription* classDescription;
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb", @"entityName=%@", entityName);
classDescription = NSMapGet(classDescriptionForEntity, entityName);
NSDebugMLLog(@"gsdb", @"classDescription=%@", classDescription);
if (!classDescription)
{
[[NSNotificationCenter defaultCenter]
postNotificationName: EOClassDescriptionNeededForEntityNameNotification
object: entityName];
classDescription = NSMapGet(classDescriptionForEntity, entityName);
NSDebugMLLog(@"gsdb", @"classDescription=%@", classDescription);
if (!classDescription)
{
NSLog(@"Warning: No class description for entity named: %@",
entityName);
}
}
EOFLOGObjectFnStop();
return classDescription;
}
+ (void)invalidateClassDescriptionCache
{
NSResetMapTable(classDescriptionForClass);
NSResetMapTable(classDescriptionForEntity);
}
+ (void)registerClassDescription: (EOClassDescription *)description
forClass: (Class)aClass
{
NSString *entityName;
EOFLOGObjectFnStart();
NSAssert(description, @"No class description");
NSAssert(aClass, @"No class");
NSDebugMLLog(@"gsdb", @"description=%@", description);
entityName = [description entityName];
//NSAssert(entityName,@"No Entity Name");
NSDebugMLLog(@"gsdb", @"entityName=%@", entityName);
NSMapInsert(classDescriptionForClass, aClass, description);
if (entityName)
{
NSMapInsert(classDescriptionForEntity, entityName, description);
}
NSDebugMLLog(@"gsdb", @"end");
EOFLOGObjectFnStop();
}
+ (void)setClassDelegate:(id)delegate
{
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb",@"delegate %p=%@", delegate, delegate);
classDelegate = delegate;
EOFLOGObjectFnStop();
}
- (NSArray *)attributeKeys
{
return nil;
}
- (void)awakeObject: (id)object
fromFetchInEditingContext: (EOEditingContext *)anEditingContext
{
//OK
//nothing to do
}
- (void)awakeObject: (id)object
fromInsertionInEditingContext: (EOEditingContext *)anEditingContext
{
//Near OK
NSArray *toManyRelationshipKeys = nil;
int toManyCount = 0;
EOFLOGObjectFnStart();
toManyRelationshipKeys = [self toManyRelationshipKeys];
toManyCount = [toManyRelationshipKeys count];
if (toManyCount > 0)
{
int i;
for (i = 0; i < toManyCount; i++)
{
id key = [toManyRelationshipKeys objectAtIndex: i];
id value = [object storedValueForKey: key];
NSDebugMLLog(@"gsdb", @"key=%@ value=%@",key,value);
if (value)
{
//Do nothing ??
}
else
{
[object takeStoredValue:[EOCheapCopyMutableArray
arrayWithCapacity: 2]
forKey: key];
}
}
}
EOFLOGObjectFnStop();
}
- (EOClassDescription *)classDescriptionForDestinationKey: (NSString *)detailKey
{
return nil;
}
- (id)createInstanceWithEditingContext: (EOEditingContext *)anEditingContext
globalID: (EOGlobalID *)globalID
zone: (NSZone *)zone
{
EOFLOGObjectFnStart();
EOFLOGObjectFnStop();
return nil;
}
- (NSFormatter *)defaultFormatterForKey: (NSString *)key
{
return nil;
}
- (NSFormatter *)defaultFormatterForKeyPath: (NSString *)keyPath
{
return nil; //TODO
}
- (EODeleteRule)deleteRuleForRelationshipKey: (NSString *)relationshipKey
{
//OK
EOFLOGObjectFnStart();
EOFLOGObjectFnStop();
return EODeleteRuleNullify;
}
- (NSString *)displayNameForKey: (NSString *)key
{
const char *s, *ckey = [key cString];
NSMutableString *str = [NSMutableString stringWithCapacity:[key length]];
char c;
BOOL init = NO;
s = ckey;
while (*s)
{
if (init && s == ckey && islower(*s))
{
c = toupper(*s);
[str appendString: [NSString stringWithCString: &c length: 1]];
}
else if (isupper(*s) && s != ckey)
{
[str appendString: [NSString stringWithCString: ckey
length: s - ckey]];
[str appendString: @" "];
ckey = s;
}
init = NO;
s++;
}
if (s != ckey)
[str appendString: [NSString stringWithCString: ckey length: s - ckey]];
return AUTORELEASE([key copy]);
}
- (NSString *)entityName
{
//OK
return nil;
}
- (NSString *)inverseForRelationshipKey: (NSString *)relationshipKey
{
return nil;
}
- (BOOL)ownsDestinationObjectsForRelationshipKey: (NSString *)relationshipKey
{
return NO;
}
- (void)propagateDeleteForObject: (id)object
editingContext: (EOEditingContext *)context
{
NSArray *toRelArray;
NSEnumerator *toRelEnum;
NSString *key; //, *inverseKey = nil;
id destination = nil;
id classDelegate;
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb",@"object %p=%@", object, object);
classDelegate = [[self class] classDelegate];
NSDebugMLLog(@"gsdb", @"classDelegate%p=%@",
classDelegate,
classDelegate);
toRelArray = [object toOneRelationshipKeys];
toRelEnum = [toRelArray objectEnumerator];
while ((key = [toRelEnum nextObject]))
{
BOOL shouldPropagate = YES;
NSDebugMLLog(@"gsdb", @"ToOne key=%@", key);
if (classDelegate)
shouldPropagate = [classDelegate shouldPropagateDeleteForObject: object
inEditingContext: context
forRelationshipKey: key];
NSDebugMLLog(@"gsdb", @"ToOne key=%@ shouldPropagate=%s", key,
(shouldPropagate ? "YES" : "NO"));
if (shouldPropagate)
{
destination = [object storedValueForKey: key];
NSDebugMLLog(@"gsdb", @"destination %p=%@",
destination, destination);
if (destination)
{
EODeleteRule deleteRule = [object deleteRuleForRelationshipKey:
key];
NSDebugMLLog(@"gsdb", @"deleteRule=%d", (int)deleteRule);
switch (deleteRule)
{
case EODeleteRuleNullify:
EOFLOGObjectLevel(@"gsdb", @"EODeleteRuleNullify");
[object removeObject: destination
fromBothSidesOfRelationshipWithKey: key];
/*
[object takeValue:nil
forKey:key];
inverseKey = [object inverseForRelationshipKey:key];
NSDebugMLLog(@"gsdb",@"inverseKey=%@",inverseKey);
if (inverseKey)
// p.ex. : the statement [employee inverseForRelationshipKey:@"department"] --> returns "employees"
[destination removeObject:object
fromPropertyWithKey:inverseKey];
*/
break;
case EODeleteRuleCascade:
//OK
EOFLOGObjectLevel(@"gsdb", @"EODeleteRuleCascade");
[object removeObject: destination
fromBothSidesOfRelationshipWithKey: key];
[context deleteObject: destination];
[destination propagateDeleteWithEditingContext: context];
break;
case EODeleteRuleDeny:
EOFLOGObjectLevel(@"gsdb", @"EODeleteRuleDeny");
// TODO don't know how to do yet, if raise an exception
// or something else.
NSEmitTODO();
[self notImplemented: _cmd];
break;
case EODeleteRuleNoAction:
EOFLOGObjectLevel(@"gsdb", @"EODeleteRuleNoAction");
break;
}
}
}
}
toRelArray = [self toManyRelationshipKeys];
toRelEnum = [toRelArray objectEnumerator];
while ((key = [toRelEnum nextObject]))
{
BOOL shouldPropagate = YES;
NSDebugMLLog(@"gsdb", @"ToMany key=%@", key);
if (classDelegate)
shouldPropagate = [classDelegate shouldPropagateDeleteForObject: object
inEditingContext: context
forRelationshipKey: key];
NSDebugMLLog(@"gsdb", @"ToMany key=%@ shouldPropagate=%s", key,
(shouldPropagate ? "YES" : "NO"));
if (shouldPropagate)
{
NSArray *toManyArray;
EODeleteRule deleteRule;
toManyArray = [object valueForKey: key];
NSDebugMLLog(@"gsdb", @"toManyArray %p=%@", toManyArray, toManyArray);
deleteRule = [object deleteRuleForRelationshipKey: key];
NSDebugMLLog(@"gsdb", @"deleteRule=%d", (int)deleteRule);
switch (deleteRule)
{
case EODeleteRuleNullify:
EOFLOGObjectLevel(@"gsdb", @"EODeleteRuleNullify");
NSDebugMLLog(@"gsdb", @"toManyArray %p=%@", toManyArray,
toManyArray);
while ((destination = [toManyArray lastObject]))
{
NSDebugMLLog(@"gsdb", @"destination %p=%@", destination,
destination);
[object removeObject: destination
fromBothSidesOfRelationshipWithKey: key];
/*
inverseKey = [self inverseForRelationshipKey:key];
NSDebugMLLog(@"gsdb",@"inverseKey=%@",inverseKey);
if (inverseKey)
[destination removeObject:object
fromPropertyWithKey:inverseKey];
*/
}
NSDebugMLLog(@"gsdb", @"toManyArray %p=%@",
toManyArray, toManyArray);
break;
case EODeleteRuleCascade:
//OK
EOFLOGObjectLevel(@"gsdb", @"EODeleteRuleCascade");
NSDebugMLLog(@"gsdb", @"toManyArray %p=%@",
toManyArray, toManyArray);
while ((destination = [toManyArray lastObject]))
{
NSDebugMLLog(@"gsdb", @"destination %p=%@",
destination, destination);
[object removeObject: destination
fromBothSidesOfRelationshipWithKey: key];
[context deleteObject: destination];
[destination propagateDeleteWithEditingContext: context];
}
NSDebugMLLog(@"gsdb", @"toManyArray %p=%@",
toManyArray, toManyArray);
break;
case EODeleteRuleDeny:
EOFLOGObjectLevel(@"gsdb", @"EODeleteRuleDeny");
NSDebugMLLog(@"gsdb", @"toManyArray %p=%@",
toManyArray, toManyArray);
if ([toManyArray count] > 0)
{
// TODO don't know how to do yet, if raise an exception
// or something else.
NSEmitTODO();
[self notImplemented: _cmd];
}
break;
case EODeleteRuleNoAction:
EOFLOGObjectLevel(@"gsdb", @"EODeleteRuleNoAction");
break;
}
}
}
EOFLOGObjectFnStop();
}
- (NSArray *)toManyRelationshipKeys
{
//OK
return nil;
}
- (NSArray *)toOneRelationshipKeys
{
//OK
return nil;
}
- (EORelationship *)relationshipNamed:(NSString *)relationshipName
{
//OK
return nil;
}
- (EORelationship *)anyRelationshipNamed:(NSString *)relationshipNamed
{
return nil;
}
- (NSString *)userPresentableDescriptionForObject:(id)anObject
{
NSArray *attrArray = [self attributeKeys];
NSEnumerator *attrEnum = [attrArray objectEnumerator];
NSMutableString *values = [NSMutableString stringWithCapacity:
4 * [attrArray count]];
NSString *key;
BOOL init = YES;
attrEnum = [attrArray objectEnumerator];
while ((key = [attrEnum nextObject]))
{
if (!init)
[values appendString: @","];
[values appendString: [[self valueForKey: key] description]];
init = NO;
}
return values;
}
- (NSException *)validateObjectForDelete: (id)object
{
return nil;
}
- (NSException *)validateObjectForSave:(id)object
{
return nil;
}
- (NSException *)validateValue: (id *)valueP
forKey: (NSString *)key
{
return nil;
}
@end
@implementation EOClassDescription (Deprecated)
+ (void)setDelegate: (id)delegate
{
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb", @"delegate %p=%@", delegate, delegate);
[EOClassDescription setClassDelegate: delegate];
EOFLOGObjectFnStop();
}
+ (id)delegate
{
return [EOClassDescription classDelegate];
}
@end
@implementation NSObject (EOInitialization)
- initWithEditingContext: (EOEditingContext *)ec
classDescription: (EOClassDescription *)classDesc
globalID: (EOGlobalID *)globalID;
{
return [self init];
}
@end
@implementation NSObject (EOClassDescriptionPrimitives)
// when you enable the NSDebugMLLogs here you will have a loop. dave
- (EOClassDescription *)classDescription
{
EOClassDescription *cd;
EOFLOGObjectFnStart();
//NSDebugMLLog(@"gsdb", @"self (%p)=%@ class=%@", self, self, [self class]);
cd = (EOClassDescription *)[EOClassDescription classDescriptionForClass:
[self class]];
//NSDebugMLLog(@"gsdb", @"classDescription=%@", cd);
EOFLOGObjectFnStop();
return cd;
}
- (NSString *)entityName
{
NSString *entityName;
EOFLOGObjectFnStart();
entityName = [[self classDescription] entityName];
EOFLOGObjectFnStop();
return entityName;
}
- (NSArray *)attributeKeys
{
NSArray *attributeKeys;
EOFLOGObjectFnStart();
attributeKeys = [[self classDescription] attributeKeys];
EOFLOGObjectFnStop();
return attributeKeys;
}
- (NSArray *)toOneRelationshipKeys
{
NSArray *toOneRelationshipKeys;
EOFLOGObjectFnStart();
toOneRelationshipKeys = [[self classDescription] toOneRelationshipKeys];
EOFLOGObjectFnStop();
return toOneRelationshipKeys;
}
- (NSArray *)toManyRelationshipKeys
{
NSArray *toManyRelationshipKeys;
EOFLOGObjectFnStart();
toManyRelationshipKeys = [[self classDescription] toManyRelationshipKeys];
EOFLOGObjectFnStop();
return toManyRelationshipKeys;
}
- (NSString *)inverseForRelationshipKey: (NSString *)relationshipKey
{
NSString *inverse;
EOFLOGObjectFnStart();
inverse = [[self classDescription]
inverseForRelationshipKey: relationshipKey];
EOFLOGObjectFnStop();
return inverse;
}
- (EODeleteRule)deleteRuleForRelationshipKey: (NSString *)relationshipKey
{
EODeleteRule rule;
EOClassDescription *cd;
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb", @"self %p=%@", self, self);
cd = [self classDescription];
NSDebugMLLog(@"gsdb", @"cd %p=%@", cd, cd);
rule = [cd deleteRuleForRelationshipKey: relationshipKey];
EOFLOGObjectFnStop();
return rule;
}
- (BOOL)ownsDestinationObjectsForRelationshipKey: (NSString *)relationshipKey
{
BOOL owns;
EOFLOGObjectFnStart();
owns = [[self classDescription]
ownsDestinationObjectsForRelationshipKey: relationshipKey];
EOFLOGObjectFnStop();
return owns;
}
- (EOClassDescription *)classDescriptionForDestinationKey:(NSString *)detailKey
{
EOClassDescription *cd;
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb", @"detailKey=%@", detailKey);
cd = [[self classDescription] classDescriptionForDestinationKey: detailKey];
EOFLOGObjectFnStop();
return cd;
}
- (NSString *)userPresentableDescription
{
NSString *userPresentableDescription = nil;
NSArray *attrArray;
NSEnumerator *attrEnum;
NSString *key;
EOFLOGObjectFnStart();
attrArray = [self attributeKeys];
attrEnum = [attrArray objectEnumerator];
while ((key = [attrEnum nextObject]))
{
if ([key isEqualToString: @"name"])
return key;
}
attrEnum = [attrArray objectEnumerator];
while ((key = [attrEnum nextObject]))
{
if ([key isEqualToString: @"name"])
return key;
}
userPresentableDescription = [[self classDescription]
userPresentableDescription];
EOFLOGObjectFnStop();
return userPresentableDescription;
}
- (NSException *)validateValue: (id *)valueP
forKey: (NSString *)key
{
NSException *exception;
EOClassDescription *selfClassDescription;
EOFLOGObjectFnStart();
NSAssert(valueP, @"No value pointer");
NSDebugMLog(@"self (%p) [of class %@]=%@", self, [self class], self);
selfClassDescription = [self classDescription];
NSDebugMLog(@"selfClassDescription=%@",selfClassDescription);
exception = [selfClassDescription validateValue: valueP
forKey: key];
if (exception)
{
exception = [NSException exceptionWithName: [exception name]
reason: [exception reason]
userInfo: [NSDictionary
dictionaryWithObjectsAndKeys:
self, @"EOValidatedObjectUserInfoKey",
key, @"EOValidatedPropertyUserInfoKey",
nil, nil]];
}
if (exception == nil)
{
NSMutableString *selString = [NSMutableString stringWithCapacity: 32];
SEL validateSelector;
const char *str;
char l;
str = [key cString];
l = str[0];
if (islower(l))
l = toupper(l);
[selString appendString: @"validate"];
[selString appendString: [NSString stringWithCString: &l length: 1]];
[selString appendString: [NSString stringWithCString: &str[1]]];
[selString appendString: @":"];
validateSelector = NSSelectorFromString(selString);
if (validateSelector && [self respondsToSelector: validateSelector])
exception = [self performSelector: validateSelector
withObject: *valueP];
}
EOFLOGObjectFnStop();
return exception;
}
- (NSException *)validateForSave
{
NSMutableArray *expArray = nil;
NSException* exception;
int which;
EOFLOGObjectFnStart();
exception = [[self classDescription] validateObjectForSave: self];
if (exception)
{
if (!expArray)
expArray = [NSMutableArray array];
[expArray addObject:exception];
}
for (which = 0; which < 3; which++)
{
NSArray *keys;
if (which == 0)
keys = [self attributeKeys];
else if (which == 1)
keys = [self toOneRelationshipKeys];
else
keys = [self toManyRelationshipKeys];
if (keys)
{
int keysCount = [keys count];
int i;
for (i = 0; i < keysCount; i++)
{
NSString *key = [keys objectAtIndex: i];
id value = [self valueForKey: key];
id newValue = value;
exception = [self validateValue: &newValue
forKey: key];
if (exception)
{
if (!expArray)
expArray = [NSMutableArray array];
[expArray addObject: exception];
}
if ([newValue isEqual: value] == NO)
[self takeValue: newValue
forKey: key];
}
}
}
EOFLOGObjectFnStop();
return [NSException aggregateExceptionWithExceptions: expArray];
}
- (NSException *)validateForDelete
{
NSException *exception;
EOFLOGObjectFnStart();
exception = [[self classDescription] validateObjectForDelete: self];
EOFLOGObjectFnStop();
return exception;
}
- (void)awakeFromInsertionInEditingContext: (EOEditingContext *)editingContext
{
EOFLOGObjectFnStart();
[[self classDescription] awakeObject: self
fromInsertionInEditingContext: editingContext];
EOFLOGObjectFnStop();
}
- (void)awakeFromFetchInEditingContext: (EOEditingContext *)editingContext
{
EOFLOGObjectFnStart();
[[self classDescription] awakeObject: self
fromFetchInEditingContext: editingContext];
EOFLOGObjectFnStop();
}
@end
@implementation NSArray (EOShallowCopy)
- (NSArray *)shallowCopy
{
return [[NSArray alloc] initWithArray: self];
}
@end
@implementation NSObject (EOClassDescriptionExtras)
- (NSDictionary *)snapshot
{
//OK Can be Improved may be by using a dictionaryinitializer
NSMutableDictionary *snapshot;
NSArray *attributeKeys;
NSArray *toOneRelationshipKeys;
NSArray *toManyRelationshipKeys;
int attributeKeyCount;
int toOneRelationshipKeyCount;
int toManyRelationshipKeyCount;
EONull *null = (EONull *)[EONull null];
int i;
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb", @"self=%@", self);
attributeKeys = [self attributeKeys];
NSDebugMLLog(@"gsdb", @"attributeKeys=%@", attributeKeys);
toOneRelationshipKeys = [self toOneRelationshipKeys];
toManyRelationshipKeys = [self toManyRelationshipKeys];
attributeKeyCount = [attributeKeys count];
toOneRelationshipKeyCount = [toOneRelationshipKeys count];
toManyRelationshipKeyCount = [toManyRelationshipKeys count];
NSDebugMLLog(@"gsdb", @"attributeKeyCount=%d toOneRelationshipKeyCount=%d toManyRelationshipKeyCount=%d",
attributeKeyCount, toOneRelationshipKeyCount,
toManyRelationshipKeyCount);
snapshot = [NSMutableDictionary dictionaryWithCapacity: attributeKeyCount
+ toOneRelationshipKeyCount
+ toManyRelationshipKeyCount];
NSDebugMLLog(@"gsdb", @"attributeKeys=%@", attributeKeys);
for (i = 0; i < attributeKeyCount; i++)
{
id key = [attributeKeys objectAtIndex: i];
id value = [self storedValueForKey: key];
if (!value)
value = null;
NSDebugMLLog(@"gsdb", @"snap=%p key=%@ ==> value %p=%@",
snapshot, key, value, value);
[snapshot setObject: value
forKey: key];
}
NSDebugMLLog(@"gsdb", @"toOneRelationshipKeys=%@", toOneRelationshipKeys);
for (i = 0; i < toOneRelationshipKeyCount; i++)
{
id key = [toOneRelationshipKeys objectAtIndex: i];
id value = [self storedValueForKey: key];
if (!value)
value = null;
NSDebugMLLog(@"gsdb", @"TOONE snap=%p key=%@ ==> value %p=%@",
snapshot, key, value, value);
[snapshot setObject: value
forKey: key];
}
NSDebugMLLog(@"gsdb", @"toManyRelationshipKeys=%@", toManyRelationshipKeys);
for (i = 0; i < toManyRelationshipKeyCount; i++)
{
id key = [toManyRelationshipKeys objectAtIndex: i];
id value = [self storedValueForKey: key];
if (value)
{
NSDebugMLLog(@"gsdb", @"TOMANY snap=%p key=%@ ==> value %p=%@",
snapshot, key, value, value);
value = AUTORELEASE([value shallowCopy]);
NSDebugMLLog(@"gsdb", @"TOMANY snap=%p key=%@ ==> value %p=%@",
snapshot, key, value, value);
[snapshot setObject: value
forKey: key];
}
/* //TODO-VERIFY or set it to eonull ?
else
value=null;
*/
}
NSDebugMLLog(@"gsdb", @"self=%p snapshot=%p", self, snapshot);
NSDebugMLLog(@"gsdb", @"self %p=%@\nsnapshot %p=%@", self, self, snapshot,
snapshot);
EOFLOGObjectFnStop();
NSDebugMLLog(@"gsdb", @"self=%p snapshot=%p count=%d",
self, snapshot, [snapshot count]);
return snapshot;
}
- (void)updateFromSnapshot: (NSDictionary *)snapshot
{
NSEnumerator *snapshotEnum = [snapshot keyEnumerator];
NSString *key;
EONull *null = (EONull *)[EONull null];
id val;
while ((key = [snapshotEnum nextObject]))
{
val = [snapshot objectForKey: key];
if ([val isEqual: null])
val = nil;
if ([val isKindOfClass: [NSArray class]])
val = AUTORELEASE([AUTORELEASE([val shallowCopy]) mutableCopy]);
[self takeStoredValue: val forKey: key];
}
}
- (BOOL)isToManyKey: (NSString *)key
{
NSArray *toMany = [self toManyRelationshipKeys];
NSEnumerator *toManyEnum = [toMany objectEnumerator];
NSString *relationship;
while ((relationship = [toManyEnum nextObject]))
{
if ([relationship isEqualToString: key])
return YES;
}
return NO;
}
- (NSException *)validateForInsert
{
NSException *exception;
EOFLOGObjectFnStart();
exception = [self validateForSave];
EOFLOGObjectFnStop();
return exception;
}
- (NSException *)validateForUpdate
{
NSException *exception;
EOFLOGObjectFnStart();
exception = [self validateForSave];
EOFLOGObjectFnStop();
return exception;
}
- (NSArray *)allPropertyKeys
{
NSArray *toOne;
NSArray *toMany;
NSArray *attr;
NSMutableArray *ret;
attr = [self attributeKeys];
toOne = [self toOneRelationshipKeys];
toMany = [self toManyRelationshipKeys];
ret = [NSMutableArray arrayWithCapacity:
[attr count] +
[toOne count] + [toMany count]];
[ret addObjectsFromArray: attr];
[ret addObjectsFromArray: toOne];
[ret addObjectsFromArray: toMany];
return ret;
}
- (void)clearProperties
{
NSArray *toOne = nil;
NSArray *toMany = nil;
NSEnumerator *relEnum = nil;
NSString *key = nil;
EOFLOGObjectFnStart();
toOne = [self toOneRelationshipKeys];
toMany = [self toManyRelationshipKeys];
relEnum = [toOne objectEnumerator];
while ((key = [relEnum nextObject]))
[self takeStoredValue: nil forKey: key];
relEnum = [toMany objectEnumerator];
while ((key = [relEnum nextObject]))
[self takeStoredValue: nil forKey: key];
EOFLOGObjectFnStop();
}
- (void)propagateDeleteWithEditingContext: (EOEditingContext *)editingContext
{
EOFLOGObjectFnStart();
[[self classDescription] propagateDeleteForObject: self
editingContext: editingContext];
EOFLOGObjectFnStop();
}
- (NSString *)eoShallowDescription
{
[self notImplemented: _cmd];
return nil; //TODO
}
- (NSString *)eoDescription
{
NSArray *attrArray = [self allPropertyKeys];
NSEnumerator *attrEnum = [attrArray objectEnumerator];
NSString *key;
NSMutableString *ret = [NSMutableString
stringWithCapacity: 5 * [attrArray count]];
[ret appendString: [NSString stringWithFormat:@"<%@ (%p)",
NSStringFromClass([self class]), self]];
while ((key = [attrEnum nextObject]))
{
[ret appendString: [NSString stringWithFormat: @" %@=%@",
key, [self valueForKey: key]]];
}
[ret appendString: [NSString stringWithFormat: @">"]];
return ret; //TODO
}
@end
@implementation NSObject (EOKeyRelationshipManipulation)
- (void)addObject: object
toPropertyWithKey: (NSString *)key
{
const char *str = NULL;
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb", @"self=%@", self);
NSDebugMLLog(@"gsdb", @"object=%@", object);
NSDebugMLLog(@"gsdb", @"key=%@", key);
str = [key cString];
NSDebugMLLog(@"gsdb", @"*+* ciao3 %@", key);
NSDebugMLLog(@"gsdb", @"*+* ciao3 %@", object);
if ([key length])
{
NSMutableString *selString = [NSMutableString stringWithCapacity: 25];
SEL addToSelector;
char l = str[0];
if (islower(l))
l = toupper(l);
[selString appendString: @"addTo"];
[selString appendString: [NSString stringWithCString: &l length: 1]];
[selString appendString: [NSString stringWithCString: &str[1]]];
[selString appendString: @":"];
addToSelector = NSSelectorFromString(selString);
if (addToSelector && [self respondsToSelector: addToSelector] == YES)
{
NSDebugMLLog(@"gsdb", @"selector=%@", selString);
[self performSelector: addToSelector
withObject: object];
}
else
{
id val = nil;
if ([self isToManyKey: key] == YES)
{
EOFLOGObjectLevel(@"gsdb", @"to many");
val = [self valueForKey: key]; //should use storedValueForKey: ?
NSDebugMLLog(@"gsdb", @"to many val=%@ (%@)", val, [val class]);
if ([val containsObject: object])
{
NSDebugMLog(@"Object %p already in too many val=%@ (%@)",
object, val, [val class]);
}
else
{
if ([val isKindOfClass: [NSMutableArray class]])
{
EOFLOGObjectLevel(@"gsdb", @"to many2");
[self willChange];
[val addObject: object];
}
else
{
NSMutableArray *relArray;
if (val)
relArray = AUTORELEASE([val mutableCopy]);
else
relArray = [NSMutableArray arrayWithCapacity: 10];
NSDebugMLLog(@"gsdb", @"relArray=%@ (%@)",
relArray, [relArray class]);
[relArray addObject: object];
NSDebugMLLog(@"gsdb", @"relArray=%@ (%@)",
relArray, [relArray class]);
[self takeValue: relArray
forKey: key];
}
}
}
else
{
EOFLOGObjectLevel(@"gsdb", @"key is not to many");
[self takeValue: object
forKey: key];
}
}
}
NSDebugMLLog(@"gsdb", @"self=%@", self);
NSDebugMLLog(@"gsdb", @"object=%@", object);
EOFLOGObjectFnStop();
}
- (void)removeObject: object
fromPropertyWithKey: (NSString *)key
{
//self valueForKey:
const char *str = NULL;
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb", @"self=%@", self);
NSDebugMLLog(@"gsdb", @"object=%@", object);
NSDebugMLLog(@"gsdb", @"key=%@ class=%@", key, [key class]);
str = [key cString];
if ([key length])
{
NSMutableString *selString = [NSMutableString stringWithCapacity: 25];
SEL removeFromSelector;
char l = str[0];
if (islower(l))
l = toupper(l);
[selString appendString: @"removeFrom"];
NSDebugMLLog(@"gsdb", @"selString=%@", selString);
[selString appendString: [NSString stringWithCString: &l
length: 1]];
NSDebugMLLog(@"gsdb", @"selString=%@", selString);
[selString appendString: [NSString stringWithCString: &str[1]]];
NSDebugMLLog(@"gsdb", @"selString=%@", selString);
[selString appendString: @":"];
NSDebugMLLog(@"gsdb", @"selString=%@", selString);
removeFromSelector = NSSelectorFromString(selString);
NSDebugMLLog(@"gsdb", @"selString=%@ removeFromSelector=%p", selString,
(void*)removeFromSelector);
if (removeFromSelector && [self respondsToSelector: removeFromSelector])
{
EOFLOGObjectLevel(@"gsdb", @"responds=YES");
[self performSelector: removeFromSelector
withObject: object];
}
else
{
id val = nil;
EOFLOGObjectLevel(@"gsdb", @"responds=NO");
if ([self isToManyKey:key] == YES)
{
EOFLOGObjectLevel(@"gsdb", @"key is to many");
val = [self valueForKey: key];
NSDebugMLLog(@"gsdb", @"val=%@", val);
if ([val isKindOfClass: [NSMutableArray class]])
{
[self willChange];
[val removeObject: object];
}
else
{
NSMutableArray *relArray = nil;
if (val)
{
relArray = AUTORELEASE([val mutableCopy]);
[relArray removeObject: object];
[self takeValue: relArray
forKey: key];
}
}
}
else
{
EOFLOGObjectLevel(@"gsdb", @"key is not to many");
[self takeValue: nil
forKey: key];
}
}
}
NSDebugMLLog(@"gsdb", @"self=%@", self);
NSDebugMLLog(@"gsdb", @"object=%@", object);
EOFLOGObjectFnStop();
}
-(void)_setObject: (id)object
forBothSidesOfRelationshipWithKey: (NSString*)key
{
//Near OK
NSString *inverseKey;
id oldObject;
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb", @"self=%@", self);
NSDebugMLLog(@"gsdb", @"object=%@", object);
NSDebugMLLog(@"gsdb", @"key=%@", key);
inverseKey = [self inverseForRelationshipKey:key];
NSDebugMLLog(@"gsdb", @"inverseKey=%@", inverseKey);
oldObject = [self valueForKey: key];
NSDebugMLLog(@"gsdb", @"oldObject=%@", oldObject);
if (inverseKey)
{
[oldObject removeObject: self
fromPropertyWithKey: inverseKey];
[object addObject: self
toPropertyWithKey: inverseKey];
/* if ([object isToManyKey:inverseKey])
{
//??
EOFLOGObjectLevel(@"gsdb",@"Inverse is to many");
[oldObject removeObject:self
fromPropertyWithKey:inverseKey];
[object addObject:self
toPropertyWithKey:inverseKey];
}
else
{
EOFLOGObjectLevel(@"gsdb",@"Inverse is not to many");
//OK
//MIRKO if ((inverseKey = [oldObject inverseForRelationshipKey:key]))
//MIRKO [oldObject removeObject:self
// fromPropertyWithKey:inverseKey];
[oldObject takeValue:nil
forKey:inverseKey];
[object takeValue:self
forKey:inverseKey];
};
*/
}
[self takeValue: object
forKey: key];
NSDebugMLLog(@"gsdb", @"self=%@", self);
NSDebugMLLog(@"gsdb", @"object=%@", object);
EOFLOGObjectFnStop();
}
- (void)addObject: (id)object
toBothSidesOfRelationshipWithKey: (NSString *)key
{
EOFLOGObjectFnStart();
NSDebugMLLog(@"gsdb", @"self=%@", self);
NSDebugMLLog(@"gsdb", @"object=%@", object);
NSDebugMLLog(@"gsdb", @"key=%@", key);
// 2 differents cases: to-one and to-many relation
if ([self isToManyKey:key]) // to-many
{
//See if there's an inverse relationship
NSString *inverseKey = [self inverseForRelationshipKey: key];
NSDebugMLLog(@"gsdb", @"self %p=%@,object %p=%@ key=%@ inverseKey=%@",
self,
self,
object,
object,
key,
inverseKey);
// First add object to self relation array
[self addObject: object
toPropertyWithKey: key];
if (inverseKey) //if no inverse relation do nothing
{
// See if inverse relationship is to-many or to-one
if ([object isToManyKey: inverseKey])
{
//TODO VERIFY
[object addObject:self
toPropertyWithKey:inverseKey];
}
else
{
// Previous value, if any
id oldObject = [object valueForKey: inverseKey];
NSDebugMLLog(@"gsdb", @"oldObject=%@", oldObject);
if (oldObject)
{
//TODO VERIFY
[object removeObject:oldObject
fromPropertyWithKey:inverseKey];
}
// Just set self into object relationship property
[object takeValue: self
forKey: inverseKey];
}
}
}
else
{
[self _setObject: object
forBothSidesOfRelationshipWithKey: key];
}
NSDebugMLLog(@"gsdb", @"self=%@", self);
NSDebugMLLog(@"gsdb", @"object=%@", object);
EOFLOGObjectFnStop();
}
- (void)removeObject: (id)object
fromBothSidesOfRelationshipWithKey: (NSString *)key
{
NSString *inverseKey;
[self removeObject: object
fromPropertyWithKey: key];
if ((inverseKey = [self inverseForRelationshipKey: key]))
[object removeObject: self
fromPropertyWithKey: inverseKey];
}
@end
@implementation NSException (EOValidationError)
+ (NSException *)validationExceptionWithFormat: (NSString *)format, ...
{
NSException *exp = nil;
NSString *aName = nil;
va_list args;
va_start(args, format);
aName = AUTORELEASE([[NSString alloc] initWithFormat: format
arguments: args]);
exp = [NSException exceptionWithName: EOValidationException
reason: aName
userInfo: nil];
va_end(args);
return exp;
}
+ (NSException *)aggregateExceptionWithExceptions: (NSArray *)subexceptions
{
NSException *exp = nil;
if ([subexceptions count] == 1)
exp = [subexceptions objectAtIndex: 0];
else if ([subexceptions count] > 1)
{
NSString *aName = nil, *aReason = nil;
NSMutableDictionary *aUserInfo = nil;
exp = [subexceptions objectAtIndex: 0];
aName = [exp name];
aReason = [exp reason];
aUserInfo = AUTORELEASE([[exp userInfo] mutableCopy]);
[aUserInfo setObject: subexceptions
forKey: EOAdditionalExceptionsKey];
exp = [NSException exceptionWithName: aName
reason: aReason
userInfo: aUserInfo];
}
return exp;
}
- (NSException *)exceptionAddingEntriesToUserInfo: (NSDictionary *)additions
{
NSException *exp = nil;
NSString *aName = nil, *aReason = nil;
NSMutableDictionary *aUserInfo = nil;
aName = [self name];
aReason = [self reason];
aUserInfo = AUTORELEASE([[self userInfo] mutableCopy]);
[aUserInfo setObject: [additions allValues]
forKey: EOValidatedObjectUserInfoKey];
[aUserInfo setObject: [additions allKeys]
forKey: EOValidatedPropertyUserInfoKey];
exp = [NSException exceptionWithName: aName
reason: aReason
userInfo: aUserInfo];
return exp;
}
@end
@implementation NSObject (EOClassDescriptionClassDelegate)
- (BOOL)shouldPropagateDeleteForObject: (id)object
inEditingContext: (EOEditingContext *)ec
forRelationshipKey: (NSString *)key
{
return YES;
}
@end
@implementation NSObject (_EOValueMerging)
- (void)mergeValue: (id)value
forKey: (id)key
{
[self notImplemented:_cmd];
return;
}
- (void)mergeChangesFromDictionary: (NSDictionary *)changes
{
[self notImplemented:_cmd];
return;
}
- (NSDictionary *)changesFromSnapshot: (NSDictionary *)snapshot
{
id propertiesList[2];
NSArray *properties;
int h, i, count;
NSMutableArray *newKeys = [NSMutableArray arrayWithCapacity: 16];
NSMutableArray *newVals = [NSMutableArray arrayWithCapacity: 16];
NSString *key;
propertiesList[0] = [self attributeKeys];
propertiesList[1] = [self toOneRelationshipKeys];
for (h = 0; h < 2; h++)
{
id val, oldVal;
properties = propertiesList[h];
count = [properties count];
for(i = 0; i < count; i++)
{
key = [properties objectAtIndex: i];
val = [self storedValueForKey: key];
oldVal = [snapshot storedValueForKey: key];
if (val == oldVal || [val isEqual: oldVal] == YES)
continue;
[newKeys addObject: key];
[newVals addObject: val];
}
}
properties = [self toManyRelationshipKeys];
count = [properties count];
for(i = 0; i < count; i++)
{
NSMutableArray *array, *objects;
NSArray *val, *oldVal;
int valCount, oldValCount;
key = [properties objectAtIndex: i];
val = [self storedValueForKey: key];
oldVal = [snapshot objectForKey: key];
if ((id)val == [EONull null])
val = nil;
if ((id)oldVal == [EONull null])
oldVal = nil;
if (!val && !oldVal)
continue;
valCount = [val count];
oldValCount = [oldVal count];
if (valCount == 0 && oldValCount == 0)
continue;
array = [NSMutableArray arrayWithCapacity: 2];
if (val && valCount>0)
{
objects = [NSMutableArray arrayWithArray: val];
[objects removeObjectsInArray: oldVal];
}
else
objects = [NSMutableArray arrayWithCapacity: 1];
[array addObject: objects];
if (val && valCount > 0)
{
objects = [NSMutableArray arrayWithArray: oldVal];
[objects removeObjectsInArray: val];
}
else
objects = [NSMutableArray arrayWithCapacity: 1];
[array addObject: objects];
[newKeys addObject: key];
[newVals addObject: array];
}
return [NSDictionary dictionaryWithObjects: newVals forKeys: newKeys];
}
- (void)reapplyChangesFromSnapshot: (NSDictionary *)changes
{
[self notImplemented: _cmd];
}
@end
@implementation NSObject (_EOEditingContext)
-(EOEditingContext*)editingContext
{
return [EOObserverCenter observerForObject: self
ofClass: [EOEditingContext class]];
}
@end