/** EOClassDescription.m EOClassDescription Class Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005 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 3 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ #include "config.h" #ifdef GNUSTEP #include #include #include #include #include #include #include #include #include #include #include #include #else #include #endif #ifndef GNUSTEP #include #include #include #endif #include #include #include #include #include #include #include #include #include #include "EOPrivate.h" // NOTE: (stephane@sente.ch) Should we subclass NSClassDescription? /* ayers@fsfe.org: 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 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; static NSRecursiveLock *local_lock = nil; @implementation EOClassDescription + (void) initialize { static BOOL initialized=NO; if (!initialized) { Class cls = Nil; initialized=YES; GDL2_PrivateInit(); cls = NSClassFromString(@"EOModelGroup"); local_lock = [NSRecursiveLock new]; classDescriptionForClass = NSCreateMapTable(NSObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 32); classDescriptionForEntity = NSCreateMapTable(NSObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 32); if (cls != Nil) [cls defaultGroup]; // Insure correct initialization. } } /* * Methods */ + (id)classDelegate { id delegate; [local_lock lock]; delegate = classDelegate; if (delegate != nil) { AUTORELEASE(RETAIN(delegate)); } [local_lock unlock]; return delegate; } + (EOClassDescription *)classDescriptionForClass:(Class)aClass { EOClassDescription *classDescription; NSAssert(aClass, @"No class"); classDescription = NSMapGet(classDescriptionForClass, aClass); if (!classDescription) { [[NSNotificationCenter defaultCenter] postNotificationName: EOClassDescriptionNeededForClassNotification object: aClass]; classDescription = NSMapGet(classDescriptionForClass, aClass); if (!classDescription) { NSLog(@"Warning: No class description for class named: %@", NSStringFromClass(aClass)); NSMapInsert(classDescriptionForClass, aClass, GDL2_EONull); } } return classDescription == (id)GDL2_EONull ? nil : classDescription; } + (EOClassDescription *)classDescriptionForEntityName: (NSString *)entityName { EOClassDescription* classDescription; classDescription = NSMapGet(classDescriptionForEntity, entityName); if (!classDescription) { [[NSNotificationCenter defaultCenter] postNotificationName: EOClassDescriptionNeededForEntityNameNotification object: entityName]; classDescription = NSMapGet(classDescriptionForEntity, entityName); if (!classDescription) { NSLog(@"Warning: No class description for entity named: %@", entityName); } } return classDescription; } + (void)invalidateClassDescriptionCache { NSResetMapTable(classDescriptionForClass); NSResetMapTable(classDescriptionForEntity); } + (void)registerClassDescription: (EOClassDescription *)description forClass: (Class)aClass { NSString *entityName; NSAssert(description, @"No class description"); NSAssert(aClass, @"No class"); entityName = [description entityName]; //NSAssert(entityName,@"No Entity Name"); NSMapInsert(classDescriptionForClass, aClass, description); if (entityName) { NSMapInsert(classDescriptionForEntity, entityName, description); } } + (void)setClassDelegate:(id)delegate { classDelegate = delegate; } - (NSArray *)attributeKeys { return nil; } /** returns a new autoreleased mutable dictionary to store properties **/ - (NSMutableDictionary *)dictionaryForInstanceProperties { // Default implementation create a new EOMKKDInitializer. But subclass // implementation like EOEntityClassDescription can (should :-) use the // same EOMKKDInitializer to save memory. NSMutableArray* classPropertyNames=nil; NSMutableDictionary* dictionary=nil; // Get class properties (attributes + relationships) classPropertyNames = [[NSMutableArray alloc] initWithArray: [self attributeKeys]]; [classPropertyNames addObjectsFromArray: [self toOneRelationshipKeys]]; [classPropertyNames addObjectsFromArray: [self toManyRelationshipKeys]]; NSAssert1([classPropertyNames count] > 0, @"No classPropertyNames in %@", self); dictionary = [EOMutableKnownKeyDictionary dictionaryWithInitializer: [[EOMKKDInitializer newWithKeyArray: classPropertyNames] autorelease]]; [classPropertyNames release]; return dictionary; } - (void)awakeObject: (id)object fromFetchInEditingContext: (EOEditingContext *)editingContext { //OK //nothing to do return; } - (void)awakeObject: (id)object fromInsertionInEditingContext: (EOEditingContext *)editingContext { //Near OK NSArray *toManyRelationshipKeys = nil; int toManyCount = 0; toManyRelationshipKeys = [self toManyRelationshipKeys]; toManyCount = [toManyRelationshipKeys count]; if (toManyCount > 0) { int i; IMP oaiIMP=NULL; IMP objectTSVFK=NULL; // takeStoredValue:forKey: IMP objectSVFK=NULL; // storedValueForKey: for (i = 0; i < toManyCount; i++) { id key = GDL2_ObjectAtIndexWithImpPtr(toManyRelationshipKeys,&oaiIMP,i); id value = GDL2_StoredValueForKeyWithImpPtr(object,&objectSVFK,key); if (value) { //Do nothing ?? } else { GDL2_TakeStoredValueForKeyWithImpPtr(object,&objectTSVFK, [EOCheapCopyMutableArray arrayWithCapacity: 2], key); } } } } - (EOClassDescription *)classDescriptionForDestinationKey: (NSString *)detailKey { return nil; } - (id)createInstanceWithEditingContext: (EOEditingContext *)editingContext globalID: (EOGlobalID *)globalID zone: (NSZone *)zone { return nil; } - (NSFormatter *)defaultFormatterForKey: (NSString *)key { return nil; } - (NSFormatter *)defaultFormatterForKeyPath: (NSString *)keyPath { return nil; //TODO } - (EODeleteRule)deleteRuleForRelationshipKey: (NSString *)relationshipKey { //OK return EODeleteRuleNullify; } - (NSString *)displayNameForKey: (NSString *)key { const char *s, *ckey = [key cString]; NSMutableString *str = [NSMutableString stringWithCapacity:[key length]]; char c; BOOL init = NO; IMP strAS=NULL; s = ckey; while (*s) { if (init && s == ckey && islower(*s)) { c = toupper(*s); GDL2_AppendStringWithImpPtr(str,&strAS, GDL2_StringWithCStringAndLength(&c,1)); } else if (isupper(*s) && s != ckey) { GDL2_AppendStringWithImpPtr(str,&strAS, GDL2_StringWithCStringAndLength(ckey,s - ckey)); GDL2_AppendStringWithImpPtr(str,&strAS,@" "); ckey = s; } init = NO; s++; } if (s != ckey) GDL2_AppendStringWithImpPtr(str,&strAS, GDL2_StringWithCStringAndLength(ckey,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 *)editingContext { NSArray *toRelArray; NSEnumerator *toRelEnum; NSString *key; //, *inverseKey = nil; id destination = nil; id classDelegate; if (object==GDL2_EONull) { NSWarnMLog(@"Warning: object is an EONull"); } else { IMP objectSVFK=NULL; // storedValueForKey: IMP objectVFK=NULL; IMP toRelEnumNO=NULL; classDelegate = [[self class] classDelegate]; NSDebugMLLog(@"gsdb", @"classDelegate%p=%@", classDelegate, classDelegate); toRelArray = [object toOneRelationshipKeys]; toRelEnum = [toRelArray objectEnumerator]; while ((key = GDL2_NextObjectWithImpPtr(toRelEnum,&toRelEnumNO))) { BOOL shouldPropagate = YES; if (classDelegate) { shouldPropagate = [classDelegate shouldPropagateDeleteForObject: object inEditingContext: editingContext forRelationshipKey: key]; } NSDebugMLLog(@"gsdb", @"ToOne key=%@ shouldPropagate=%s", key, (shouldPropagate ? "YES" : "NO")); if (shouldPropagate) { destination = GDL2_StoredValueForKeyWithImpPtr(object,&objectSVFK,key); NSDebugMLLog(@"gsdb", @"destination %p=%@", destination, destination); if (!_isNilOrEONull(destination)) { EODeleteRule deleteRule = [object deleteRuleForRelationshipKey: key]; switch (deleteRule) { case EODeleteRuleNullify: [(EOCustomObject*) object removeObject: destination fromBothSidesOfRelationshipWithKey: key]; /* [object takeValue:nil forKey:key]; inverseKey = [object inverseForRelationshipKey:key]; if (inverseKey) // p.ex. : the statement [employee inverseForRelationshipKey:@"department"] --> returns "employees" [destination removeObject:object fromPropertyWithKey:inverseKey]; */ break; case EODeleteRuleCascade: //OK [object removeObject: destination fromBothSidesOfRelationshipWithKey: key]; [editingContext deleteObject: destination]; [destination propagateDeleteWithEditingContext: editingContext]; break; case EODeleteRuleDeny: // TODO don't know how to do yet, if raise an exception // or something else. NSEmitTODO(); [self notImplemented: _cmd]; break; case EODeleteRuleNoAction: break; } } } } toRelArray = [self toManyRelationshipKeys]; toRelEnum = [toRelArray objectEnumerator]; toRelEnumNO=NULL; while ((key = GDL2_NextObjectWithImpPtr(toRelEnum,&toRelEnumNO))) { BOOL shouldPropagate = YES; if (classDelegate) { shouldPropagate = [classDelegate shouldPropagateDeleteForObject: object inEditingContext: editingContext forRelationshipKey: key]; } NSDebugMLLog(@"gsdb", @"ToMany key=%@ shouldPropagate=%s", key, (shouldPropagate ? "YES" : "NO")); if (shouldPropagate) { NSArray *toManyArray; IMP toManyArrayLO=NULL; EODeleteRule deleteRule; toManyArray = GDL2_ValueForKeyWithImpPtr(object,&objectVFK,key); deleteRule = [object deleteRuleForRelationshipKey: key]; switch (deleteRule) { case EODeleteRuleNullify: NSDebugMLLog(@"gsdb", @"toManyArray %p=%@", toManyArray, toManyArray); while ((destination = GDL2_LastObjectWithImpPtr(toManyArray,&toManyArrayLO))) { NSDebugMLLog(@"gsdb", @"destination %p=%@", destination, destination); [object removeObject: destination fromBothSidesOfRelationshipWithKey: key]; /* inverseKey = [self inverseForRelationshipKey:key]; if (inverseKey) [destination removeObject:object fromPropertyWithKey:inverseKey]; */ } NSDebugMLLog(@"gsdb", @"toManyArray %p=%@", toManyArray, toManyArray); break; case EODeleteRuleCascade: //OK NSDebugMLLog(@"gsdb", @"toManyArray %p=%@", toManyArray, toManyArray); while ((destination = GDL2_LastObjectWithImpPtr(toManyArray,&toManyArrayLO))) { NSDebugMLLog(@"gsdb", @"destination %p=%@", destination, destination); [object removeObject: destination fromBothSidesOfRelationshipWithKey: key]; [editingContext deleteObject: destination]; [destination propagateDeleteWithEditingContext: editingContext]; } NSDebugMLLog(@"gsdb", @"toManyArray %p=%@", toManyArray, toManyArray); break; case 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: break; } } } } } - (NSArray *)toManyRelationshipKeys { //OK return nil; } - (NSArray *)toOneRelationshipKeys { //OK return nil; } - (EORelationship *)relationshipNamed:(NSString *)relationshipName { //OK return nil; } - (EORelationship *)anyRelationshipNamed:(NSString *)relationshipName { return nil; } - (NSString *)userPresentableDescriptionForObject:(id)object { NSArray *attrArray; NSEnumerator *attrEnum; NSMutableString *values; NSString *key; BOOL init = YES; attrArray = [self attributeKeys]; attrEnum = [attrArray objectEnumerator]; values = [NSMutableString stringWithCapacity: 4 * [attrArray count]]; 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 { [EOClassDescription setClassDelegate: delegate]; } + (id)delegate { return [EOClassDescription classDelegate]; } @end @interface GDL2CDNSObject @end @interface GDL2CDNSObject (EOClassDescription) + (EOClassDescription*) classDescriptionForClass: (Class)aClass; - (id) valueForKey: (NSString*)aKey; - (void) takeValue: (id)anObject forKey: (NSString*)aKey; @end @implementation NSArray (EOShallowCopy) - (NSArray *)shallowCopy { return [[NSArray alloc] initWithArray: self]; } @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 *)editingContext forRelationshipKey: (NSString *)key { return YES; } @end