/** EOKeyValueCoding.m EOKeyValueCoding Copyright (C) 1996-2002, 2003 Free Software Foundation, Inc. Author: Mircea Oancea Date: November 1996 Author: Mirko Viviani Date: February 2000 Author: Manuel Guesdon Date: January 2002 Author: David Ayers Date: February 2003 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 #include #else #include #endif #ifndef GNUSTEP #include #include #endif #include #include #include #include #include static EONull *null = nil; static SEL oaiSel; static BOOL strictWO; static inline void initialize(void) { if (null == nil) { null = [EONull null]; oaiSel = @selector(objectAtIndex:); strictWO = GSUseStrictWO451Compatibility(nil); } } /* This macro is only used locally in defined places so for the sake of efficiency, we don't use the do {} while (0) pattern. */ #define INITIALIZE if (null == nil) initialize(); /* * This dummy class exists to provide a replacement implementation for * NSObject -unableToSetNilForKey:, which calls -unableToSetNullForKey: * as defined in WO4.5. We need this mechanism as a category cannot * reliably override the category in gnustep-base or Foundation. */ @interface NilToNull : NSObject @end @interface NilToNull (SurrpressWarning) - (void) unableToSetNullForKey: (NSString *)key; @end @implementation NilToNull + (void)load { Class cls; SEL sel; IMP imp; GSMethod method; imp = NULL; sel = @selector(unableToSetNilForKey:); cls = GSClassFromName("NSObject"); method = GSGetInstanceMethodNotInherited(self, sel); if (method != METHOD_NULL) { imp = method->method_imp; } else { fprintf(stderr, "%s: Could not find method unableToSetNilForKey: in NilToNil!\n", __FILE__); abort(); } method = GSGetInstanceMethodNotInherited(cls, sel); if (method != METHOD_NULL) { method->method_imp = imp; } else { fprintf(stderr, "%s: Could not find method unableToSetNilForKey: in NSObject!\n", __FILE__); abort(); } GSFlushMethodCacheForClass(cls); } - (void) unableToSetNilForKey: (NSString *)key { [self unableToSetNullForKey: key]; } @end @implementation NSObject (_EOKeyValueCodingCompatibility) /* See EODeprecated.h. */ + (void) flushClassKeyBindings { } /* See header file for documentation. */ + (void) flushAllKeyBindings { } /* See header file for documentation. */ - (void) unableToSetNullForKey: (NSString *)key { [NSException raise: NSInvalidArgumentException format: @"%@ -- %@ 0x%x: Given nil value to set for key \"%@\"", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self, key]; } @end @implementation NSArray (EOKeyValueCoding) /** * EOKeyValueCoding protocol
* This overrides NSObjects implementation of this method. * Generally this method returns an array of objects * returned by invoking [NSObject-valueForKey:] * for each item in the receiver, substituting EONull for nil. * Keys formated like "@function.someKey" are resolved by invoking * [NSArray-computeFuncionWithKey:] "someKey" on the reviever. * The following functions are supported by default: * * @sum -> -computeSumForKey: * @avg -> -computeAvgForKey: * @max -> -computeMaxForKey: * @min -> -computeMinForKey: * @count -> -computeCountForKey: * * As a special case the @count function does not require a key, * in fact, any key supplied is ignored. * As another special case the key "count" is not forwarded to each object * of the receiver but returns the number of objects of the receiver.
* There is no special handling of EONull. Therefore expect exceptions * on EONull not responding to decimalValue and compare: when the are * used with this mechanism. */ - (id)valueForKey: (NSString *)key { id result; INITIALIZE; EOFLOGObjectFnStartCond(@"EOKVC"); if ([key isEqualToString: @"count"] || [key isEqualToString: @"@count"]) { result = [NSDecimalNumber numberWithUnsignedInt: [self count]]; } else if ([key hasPrefix:@"@"]) { NSString *selStr; SEL sel; NSRange r; r = [key rangeOfString:@"."]; NSAssert(r.location!=NSNotFound, @"Invalid computational key structure"); r.length = r.location - 1; /* set length of key (w/o @) */ r.location = 1; /* remove leading '@' */ selStr = [NSString stringWithFormat: @"compute%@ForKey:", [[key substringWithRange: r] capitalizedString]]; sel = NSSelectorFromString(selStr); NSAssert(sel!=NULL,@"Invalid computational key"); result = [self performSelector: sel /* skip located '.' */ withObject: [key substringFromIndex: NSMaxRange(r) + 1]]; } else { result = [self resultsOfPerformingSelector: @selector(valueForKey:) withObject: key defaultResult: null]; } EOFLOGObjectFnStopCond(@"EOKVC"); return result; } /** * EOKeyValueCoding protocol
* Returns the object returned by invoking [NSObject-valueForKeyPath:] * on the object returned by invoking [NSObject-valueForKey:] * on the reciever with the first key component supplied by the key path, * with rest of the key path.
* If the first component starts with "@", the first component includes the key * of the computational key component and as the form "@function.key". * If there is only one key component, this method invokes * [NSObject-valueForKey:] in the receiver with that component. * All computational components are expected to specifiy a key with the * exception of @count, in which case the key maybe omitted. * Unlike the reference implementation GDL2 allows you to continue the keyPath * in a meaningfull way after @count but the path must then contain a key as * the computational key structure implies. * (i.e. you may use "@count.self.decimalValue") The actual key "self" is * infact ignored during the computation, but the formal structure must be * maintained.
* It should be mentioned that the reference implementation * would return the result of "@count" independent * of any additional key paths, even if they were meaningless like * "@count.bla.strange". GDL2 will raise, if the object returned by * valueForKey:@"count.bla" (which generally is an NSDecimalNumber) raises on * valueForKey:@"strange". */ - (id)valueForKeyPath: (NSString *)keyPath { NSRange r; id result; EOFLOGObjectFnStartCond(@"EOKVC"); r = [keyPath rangeOfString: @"."]; if ([keyPath hasPrefix: @"@"] == YES && [keyPath isEqualToString: @"@count"] == NO) { NSRange rr; unsigned length; length = [keyPath length]; NSAssert1(r.location!=NSNotFound && length > r.location, @"invalid computational keyPath:%@", keyPath); rr.location = NSMaxRange(r); rr.length = length - rr.location; r = [keyPath rangeOfString: @"." options: 0 range: rr]; } if (r.length == 0) { result = [self valueForKey: keyPath]; } else { NSString *key = [keyPath substringToIndex: r.location]; NSString *path = [keyPath substringFromIndex: NSMaxRange(r)]; result = [[self valueForKey: key] valueForKeyPath: path]; } EOFLOGObjectFnStopCond(@"EOKVC"); return result; } /** * Iterates over the objects of the receiver send each object valueForKey: * with the parameter. The decimalValue of the returned object is accumalted. * An empty array returns NSDecimalNumber 0. */ - (id)computeSumForKey: (NSString *)key { NSDecimalNumber *ret; NSDecimal result, left, right; NSRoundingMode mode; unsigned int i, count; IMP oai; INITIALIZE; EOFLOGObjectFnStartCond(@"EOKVC"); mode = [[NSDecimalNumber defaultBehavior] roundingMode]; oai = [self methodForSelector: oaiSel]; count = [self count]; NSDecimalFromComponents(&result, 0, 0, NO); for (i=0; i 0) { id current,currentVal; IMP oai; oai = [self methodForSelector: oaiSel]; for(i=0; i 0) { id current, currentVal; IMP oai; oai = [self methodForSelector: oaiSel]; for(i=0; i 0) { id tmpKey; //EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray); tmpKey = [keyPathArray objectAtIndex: 0]; //EOFLOGObjectLevelArgs(@"EOKVC", @"tmpKey=%@", tmpKey); [keyPathArray removeObjectAtIndex: 0]; if ([key length] > 0) [key appendString: @"."]; if ([tmpKey hasSuffix: @"'"]) { tmpKey = [tmpKey stringByDeletingSuffix: @"'"]; [key appendString: tmpKey]; break; } else [key appendString: tmpKey]; //EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key); } //EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key); value = [self valueForKey: key]; //EOFLOGObjectLevelArgs(@"EOKVC",@"key=%@ tmpValue: %p (class=%@)", // key,value,[value class]); if (value && [keyPathArray count] > 0) { NSString *rightKeyPath = [keyPathArray componentsJoinedByString: @"."]; //EOFLOGObjectLevelArgs(@"EOKVC", @"rightKeyPath=%@", // rightKeyPath); value = [value valueForKeyPath: rightKeyPath]; } } else { /* * Return super valueForKeyPath: only * if there's no object for entire key keyPath */ value = [self objectForKey: keyPath]; EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=%@ tmpValue: %p (class=%@)", keyPath,value,[value class]); if (!value) value = [super valueForKeyPath: keyPath]; } //EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=%@ value: %p (class=%@)", // keyPath,value,[value class]); EOFLOGObjectFnStopCond(@"EOKVC"); return value; } /** * First checks whether the entire keyPath is contained as a key * in the receiver before invoking super's implementation. * (The special quoted key handling will probably be moved * to a GSWDictionary subclass to be used by GSWDisplayGroup.) */ - (id)storedValueForKeyPath: (NSString*)keyPath { id value = nil; INITIALIZE; EOFLOGObjectFnStartCond(@"EOKVC"); //EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=\"%@\"", // keyPath); if ([keyPath hasPrefix: @"'"] && strictWO == NO) //user defined composed key { NSMutableArray *keyPathArray = [[[[keyPath stringByDeletingPrefix: @"'"] componentsSeparatedByString: @"."] mutableCopy] autorelease]; NSMutableString *key = [NSMutableString string]; //EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray); while ([keyPathArray count] > 0) { id tmpKey; //EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray); tmpKey = [keyPathArray objectAtIndex: 0]; //EOFLOGObjectLevelArgs(@"EOKVC", @"tmpKey=%@", tmpKey); [keyPathArray removeObjectAtIndex: 0]; if ([key length] > 0) [key appendString: @"."]; if ([tmpKey hasSuffix: @"'"]) { tmpKey = [tmpKey stringByDeletingSuffix: @"'"]; [key appendString: tmpKey]; break; } else [key appendString: tmpKey]; //EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key); } //EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key); value = [self storedValueForKey: key]; //EOFLOGObjectLevelArgs(@"EOKVC",@"key=%@ tmpValue: %p (class=%@)", // key,value,[value class]); if (value && [keyPathArray count] > 0) { NSString *rightKeyPath = [keyPathArray componentsJoinedByString: @"."]; EOFLOGObjectLevelArgs(@"EOKVC", @"rightKeyPath=%@", rightKeyPath); value = [value storedValueForKeyPath: rightKeyPath]; } } else { /* * Return super valueForKeyPath: only * if there's no object for entire key keyPath */ value = [self objectForKey: keyPath]; //EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=%@ tmpValue: %p (class=%@)", // keyPath,value,[value class]); if (!value) value = [super storedValueForKeyPath: keyPath]; } //EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=%@ value: %p (class=%@)", // keyPath,value,[value class]); EOFLOGObjectFnStopCond(@"EOKVC"); return value; } @end @interface NSMutableDictionary(EOKeyValueCodingPrivate) - (void)takeValue: (id)value forKeyPath: (NSString *)keyPath isSmart: (BOOL)smartFlag; @end @implementation NSMutableDictionary (EOKVCGNUstepExtensions) /** * Method to augment the NSKeyValueCoding implementation * to account for added functionality such as quoted key paths. * (The special quoted key handling will probably be moved * to a GSWDictionary subclass to be used by GSWDisplayGroup. * this method then becomes obsolete.) */ - (void)smartTakeValue: (id)value forKeyPath: (NSString*)keyPath { [self takeValue:value forKeyPath:keyPath isSmart:YES]; } /** * Overrides gnustep-base and Foundations implementation * to account for added functionality such as quoted key paths. * (The special quoted key handling will probably be moved * to a GSWDictionary subclass to be used by GSWDisplayGroup. * this method then becomes obsolete.) */ - (void)takeValue: (id)value forKeyPath: (NSString *)keyPath { [self takeValue:value forKeyPath:keyPath isSmart:NO]; } /** * Support method to augment the NSKeyValueCoding implementation * to account for added functionality such as quoted key paths. * (The special quoted key handling will probably be moved * to a GSWDictionary subclass to be used by GSWDisplayGroup. * this method then becomes obsolete.) */ - (void)takeValue: (id)value forKeyPath: (NSString *)keyPath isSmart: (BOOL)smartFlag { EOFLOGObjectFnStartCond(@"EOKVC"); //EOFLOGObjectLevelArgs(@"EOKVC", @"keyPath=\"%@\"", // keyPath); INITIALIZE; if ([keyPath hasPrefix: @"'"] && strictWO == NO) //user defined composed key { NSMutableArray *keyPathArray = [[[[keyPath stringByDeletingPrefix: @"'"] componentsSeparatedByString: @"."] mutableCopy] autorelease]; NSMutableString *key = [NSMutableString string]; //EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray); while ([keyPathArray count] > 0) { id tmpKey; //EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray); tmpKey = RETAIN([keyPathArray objectAtIndex: 0]); //EOFLOGObjectLevelArgs(@"EOKVC", @"tmpKey=%@", tmpKey); [keyPathArray removeObjectAtIndex: 0]; if ([key length] > 0) [key appendString: @"."]; if ([tmpKey hasSuffix: @"'"]) { ASSIGN(tmpKey, [tmpKey stringByDeletingSuffix: @"'"]); [key appendString: tmpKey]; break; } else [key appendString: tmpKey]; RELEASE(tmpKey); //EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key); } //EOFLOGObjectLevelArgs(@"EOKVC",@"key=%@",key); //EOFLOGObjectLevelArgs(@"EOKVC",@"left keyPathArray=\"%@\"", // keyPathArray); if ([keyPathArray count] > 0) { id obj = [self objectForKey: key]; if (obj) { NSString *rightKeyPath = [keyPathArray componentsJoinedByString: @"."]; //EOFLOGObjectLevelArgs(@"EOKVC",@"rightKeyPath=\"%@\"", // rightKeyPath); if (smartFlag) [obj smartTakeValue: value forKeyPath: rightKeyPath]; else [obj takeValue: value forKeyPath: rightKeyPath]; } } else { if (value) [self setObject: value forKey: key]; else [self removeObjectForKey: key]; } } else { if (value == nil) { [self removeObjectForKey: keyPath]; } else { [self setObject: value forKey: keyPath]; } } EOFLOGObjectFnStopCond(@"EOKVC"); } /** * Calls [NSMutableDictionary-setObject:forKey:] using the full keyPath * as a key, if the value is non nil. Otherwise calls * [NSDictionary-removeObjectForKey:] with the full keyPath. * (The special quoted key handling will probably be moved * to a GSWDictionary subclass to be used by GSWDisplayGroup.) */ - (void)takeStoredValue: (id)value forKeyPath: (NSString *)keyPath { EOFLOGObjectFnStartCond(@"EOKVC"); //EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=\"%@\"", // keyPath); if ([keyPath hasPrefix: @"'"]) //user defined composed key { NSMutableArray *keyPathArray = [[[[keyPath stringByDeletingPrefix: @"'"] componentsSeparatedByString: @"."] mutableCopy] autorelease]; NSMutableString *key = [NSMutableString string]; //EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray); while ([keyPathArray count] > 0) { id tmpKey; //EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray); tmpKey = [keyPathArray objectAtIndex: 0]; //EOFLOGObjectLevelArgs(@"EOKVC", @"tmpKey=%@", tmpKey); [keyPathArray removeObjectAtIndex: 0]; if ([key length] > 0) [key appendString: @"."]; if ([tmpKey hasSuffix: @"'"]) { tmpKey = [tmpKey stringByDeletingSuffix: @"'"]; [key appendString: tmpKey]; break; } else [key appendString: tmpKey]; //EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key); } //EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key); //EOFLOGObjectLevelArgs(@"EOKVC",@"left keyPathArray=\"%@\"", // keyPathArray); if ([keyPathArray count] > 0) { id obj = [self objectForKey: key]; if (obj) { NSString *rightKeyPath = [keyPathArray componentsJoinedByString: @"."]; //EOFLOGObjectLevelArgs(@"EOKVC",@"rightKeyPath=\"%@\"", // rightKeyPath); [obj takeStoredValue: value forKeyPath: rightKeyPath]; } } else { if (value) [self setObject: value forKey: key]; else [self removeObjectForKey: key]; } } else { if (value) [self setObject: value forKey: keyPath]; else [self removeObjectForKey: keyPath]; } EOFLOGObjectFnStopCond(@"EOKVC"); } @end @implementation NSObject (EOKVCGNUstepExtensions) /** * This is a GDL2 extension. This convenience method iterates over * the supplied keyPaths and determines the corresponding values by invoking * valueForKeyPath: on the receiver. The results are returned an NSDictionary * with the keyPaths as keys and the returned values as the dictionary's * values. If valueForKeyPath: returns nil, it is replaced by the shared * EONull instance. */ - (NSDictionary *)valuesForKeyPaths: (NSArray *)keyPaths { NSDictionary *values = nil; int i; int n; NSMutableArray *newKeyPaths; NSMutableArray *newVals; INITIALIZE; EOFLOGObjectFnStartCond(@"EOKVC"); n = [keyPaths count]; newKeyPaths = AUTORELEASE([[NSMutableArray alloc] initWithCapacity: n]); newVals = AUTORELEASE([[NSMutableArray alloc] initWithCapacity: n]); for (i = 0; i < n; i++) { id keyPath = [keyPaths objectAtIndex: i]; id val = nil; NS_DURING //DEBUG Only ? { val = [self valueForKeyPath: keyPath]; } NS_HANDLER { NSLog(@"KVC:%@ EXCEPTION %@", NSStringFromSelector(_cmd), localException); NSDebugMLog(@"KVC:%@ EXCEPTION %@", NSStringFromSelector(_cmd), localException); [localException raise]; } NS_ENDHANDLER; if (val == nil) { val = null; } [newKeyPaths addObject: keyPath]; [newVals addObject: val]; } values = [NSDictionary dictionaryWithObjects: newVals forKeys: newKeyPaths]; EOFLOGObjectFnStopCond(@"EOKVC"); return values; } /** * This is a GDL2 extension. This convenience method retrieves the object * obtained by invoking valueForKey: on each path component until the one * next to the last. It then invokes takeStoredValue:forKey: on that object * with the last path component as the key. */ - (void)takeStoredValue: value forKeyPath: (NSString *)key { NSArray *pathArray; NSString *path; id obj = self; int i, count; EOFLOGObjectFnStartCond(@"EOKVC"); pathArray = [key componentsSeparatedByString:@"."]; count = [pathArray count]; for (i = 0; i < (count - 1); i++) { path = [pathArray objectAtIndex: i]; obj = [obj valueForKey: path]; } path = [pathArray lastObject]; [obj takeStoredValue: value forKey: path]; EOFLOGObjectFnStopCond(@"EOKVC"); } /** * This is a GDL2 extension. This convenience method retrieves the object * obtained by invoking valueForKey: on each path component until the one * next to the last. It then invokes storedValue:forKey: on that object * with the last path component as the key, returning the result. */ - (id)storedValueForKeyPath: (NSString *)key { NSArray *pathArray = nil; NSString *path; id obj = self; int i, count; EOFLOGObjectFnStartCond(@"EOKVC"); pathArray = [key componentsSeparatedByString:@"."]; count = [pathArray count]; for(i=0; i < (count-1); i++) { path = [pathArray objectAtIndex:i]; obj = [obj valueForKey:path]; } path = [pathArray lastObject]; obj=[obj storedValueForKey:path]; EOFLOGObjectFnStopCond(@"EOKVC"); return obj; } /** * This is a GDL2 extension. This convenience method iterates over * the supplied keyPaths and determines the corresponding values by invoking * storedValueForKeyPath: on the receiver. The results are returned an * NSDictionary with the keyPaths as keys and the returned values as the * dictionary's values. If storedValueForKeyPath: returns nil, it is replaced * by the shared EONull instance. */ - (NSDictionary *)storedValuesForKeyPaths: (NSArray *)keyPaths { NSDictionary *values = nil; int i, n; NSMutableArray *newKeyPaths = nil; NSMutableArray *newVals = nil; INITIALIZE; EOFLOGObjectFnStartCond(@"EOKVC"); n = [keyPaths count]; newKeyPaths = [[[NSMutableArray alloc] initWithCapacity: n] autorelease]; newVals = [[[NSMutableArray alloc] initWithCapacity: n] autorelease]; for (i = 0; i < n; i++) { id keyPath = [keyPaths objectAtIndex: i]; id val = nil; NS_DURING //DEBUG Only ? { val = [self storedValueForKeyPath: keyPath]; } NS_HANDLER { NSLog(@"EXCEPTION %@", localException); NSDebugMLog(@"EXCEPTION %@", localException); [localException raise]; } NS_ENDHANDLER; if (val == nil) val = null; [newKeyPaths addObject: keyPath]; [newVals addObject: val]; } values = [NSDictionary dictionaryWithObjects: newVals forKeys: newKeyPaths]; EOFLOGObjectFnStopCond(@"EOKVC"); return values; } /** * This is a GDL2 extension. Simply invokes takeValue:forKey:. * This method provides a hook for EOGenericRecords KVC implementation, * which takes relationship definitions into account. */ - (void)smartTakeValue: (id)anObject forKey: (NSString *)aKey { [self takeValue: anObject forKey: aKey]; } /** * This is a GDL2 extension. This convenience method invokes * smartTakeValue:forKeyPath on the object returned by valueForKey: with * the first path component. * obtained by invoking valueForKey: on each path component until the one * next to the last. It then invokes storedValue:forKey: on that object * with the last path component as the key, returning the result. */ - (void)smartTakeValue: (id)anObject forKeyPath: (NSString *)aKeyPath { NSRange r = [aKeyPath rangeOfString: @"."]; if (r.length == 0) { [self smartTakeValue: anObject forKey: aKeyPath]; } else { NSString *key = [aKeyPath substringToIndex: r.location]; NSString *path = [aKeyPath substringFromIndex: NSMaxRange(r)]; [[self valueForKey: key] smartTakeValue: anObject forKeyPath: path]; } } @end