/** Implementation of KeyValueCoding for GNUStep Copyright (C) 2000,2002 Free Software Foundation, Inc. Written by: Richard Frith-Macdonald This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. NSKeyValueCoding informal protocol reference $Date$ $Revision$ */ #import "common.h" #import "Foundation/NSArray.h" #import "Foundation/NSAutoreleasePool.h" #import "Foundation/NSDictionary.h" #import "Foundation/NSEnumerator.h" #import "Foundation/NSException.h" #import "Foundation/NSKeyValueCoding.h" #import "Foundation/NSMethodSignature.h" #import "Foundation/NSNull.h" #import "Foundation/NSSet.h" #import "Foundation/NSValue.h" /* For the NSKeyValueMutableArray and NSKeyValueMutableSet classes */ #include "NSKeyValueMutableArray.m" #include "NSKeyValueMutableSet.m" #if defined(__OBJC2__) #import "NSKeyValueCoding+Caching.h" #endif /* this should move into autoconf once it's accepted */ #define WANT_DEPRECATED_KVC_COMPAT 1 #ifdef WANT_DEPRECATED_KVC_COMPAT static IMP takePath = 0; static IMP takeValue = 0; static IMP takePathKVO = 0; static IMP takeValueKVO = 0; static inline void setupCompat() { if (takePath == 0) { Class c = NSClassFromString(@"GSKVOBase"); takePathKVO = [c instanceMethodForSelector: @selector(takeValue:forKeyPath:)]; takePath = [NSObject instanceMethodForSelector: @selector(takeValue:forKeyPath:)]; takeValueKVO = [c instanceMethodForSelector: @selector(takeValue:forKey:)]; takeValue = [NSObject instanceMethodForSelector: @selector(takeValue:forKey:)]; } } #endif static void SetValueForKey(NSObject *self, id anObject, const char *key, unsigned size) { SEL sel = 0; const char *type = 0; int off = 0; if (size > 0) { const char *name; char buf[size + 6]; char lo; char hi; memcpy(buf, "_set", 4); memcpy(&buf[4], key, size); lo = buf[4]; hi = islower(lo) ? toupper(lo) : lo; buf[4] = hi; buf[size + 4] = ':'; buf[size + 5] = '\0'; name = &buf[1]; // setKey: type = NULL; sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { name = buf; // _setKey: sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { sel = 0; if ([[self class] accessInstanceVariablesDirectly] == YES) { buf[size + 4] = '\0'; buf[3] = '_'; buf[4] = lo; name = &buf[3]; // _key if (GSObjCFindVariable(self, name, &type, &size, &off) == NO) { buf[4] = hi; buf[3] = 's'; buf[2] = 'i'; buf[1] = '_'; name = &buf[1]; // _isKey if (GSObjCFindVariable(self, name, &type, &size, &off) == NO) { buf[4] = lo; name = &buf[4]; // key if (GSObjCFindVariable(self, name, &type, &size, &off) == NO) { buf[4] = hi; buf[3] = 's'; buf[2] = 'i'; name = &buf[2]; // isKey GSObjCFindVariable(self, name, &type, &size, &off); } } } } } else { GSOnceFLog(@"Key-value access using _setKey: is deprecated:"); } } } GSObjCSetVal(self, key, anObject, sel, type, size, off); } #if !defined(__OBJC2__) static id ValueForKey(NSObject *self, const char *key, unsigned size) { SEL sel = 0; int off = 0; const char *type = NULL; if (size > 0) { const char *name; char buf[size + 5]; char lo; char hi; memcpy(buf, "_get", 4); memcpy(&buf[4], key, size); buf[size + 4] = '\0'; lo = buf[4]; hi = islower(lo) ? toupper(lo) : lo; buf[4] = hi; name = &buf[1]; // getKey sel = sel_getUid(name); if ([self respondsToSelector: sel] == NO) { buf[4] = lo; name = &buf[4]; // key sel = sel_getUid(name); if ([self respondsToSelector: sel] == NO) { buf[4] = hi; buf[3] = 's'; buf[2] = 'i'; name = &buf[2]; // isKey sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { sel = 0; } } } if (sel == 0 && [[self class] accessInstanceVariablesDirectly] == YES) { buf[4] = hi; name = buf; // _getKey sel = sel_getUid(name); if ([self respondsToSelector: sel] == NO) { buf[4] = lo; buf[3] = '_'; name = &buf[3]; // _key sel = sel_getUid(name); if ([self respondsToSelector: sel] == NO) { sel = 0; } } if (sel == 0) { if (GSObjCFindVariable(self, name, &type, &size, &off) == NO) { buf[4] = hi; buf[3] = 's'; buf[2] = 'i'; buf[1] = '_'; name = &buf[1]; // _isKey if (!GSObjCFindVariable(self, name, &type, &size, &off)) { buf[4] = lo; name = &buf[4]; // key if (!GSObjCFindVariable(self, name, &type, &size, &off)) { buf[4] = hi; buf[3] = 's'; buf[2] = 'i'; name = &buf[2]; // isKey GSObjCFindVariable(self, name, &type, &size, &off); } } } } } } return GSObjCGetVal(self, key, sel, type, size, off); } #endif @implementation NSObject(KeyValueCoding) + (BOOL) accessInstanceVariablesDirectly { return YES; } - (NSDictionary*) dictionaryWithValuesForKeys: (NSArray*)keys { NSMutableDictionary *dictionary; NSEnumerator *enumerator; id key; #ifdef WANT_DEPRECATED_KVC_COMPAT static IMP o = 0; /* Backward compatibility hack */ if (o == 0) { o = [NSObject instanceMethodForSelector: @selector(valuesForKeys:)]; } if ([self methodForSelector: @selector(valuesForKeys:)] != o) { return [self valuesForKeys: keys]; } #endif dictionary = [NSMutableDictionary dictionaryWithCapacity: [keys count]]; enumerator = [keys objectEnumerator]; while ((key = [enumerator nextObject]) != nil) { id value = [self valueForKey: key]; if (value == nil) { value = [NSNull null]; } [dictionary setObject: value forKey: key]; } return dictionary; } - (NSMutableSet*) mutableSetValueForKey: (NSString*)aKey { return [NSKeyValueMutableSet setForKey: aKey ofObject: self]; } - (NSMutableSet*) mutableSetValueForKeyPath: (NSString*)aKey { NSRange r = [aKey rangeOfString: @"." options: NSLiteralSearch]; if (r.length == 0) { return [self mutableSetValueForKey: aKey]; } else { NSString *key = [aKey substringToIndex: r.location]; NSString *path = [aKey substringFromIndex: NSMaxRange(r)]; return [[self valueForKey: key] mutableSetValueForKeyPath: path]; } } - (NSMutableArray*) mutableArrayValueForKey: (NSString*)aKey { return [NSKeyValueMutableArray arrayForKey: aKey ofObject: self]; } - (NSMutableArray*) mutableArrayValueForKeyPath: (NSString*)aKey { NSRange r = [aKey rangeOfString: @"." options: NSLiteralSearch]; if (r.length == 0) { return [self mutableArrayValueForKey: aKey]; } else { NSString *key = [aKey substringToIndex: r.location]; NSString *path = [aKey substringFromIndex: NSMaxRange(r)]; return [[self valueForKey: key] mutableArrayValueForKeyPath: path]; } } - (void) setNilValueForKey: (NSString*)aKey { #ifdef WANT_DEPRECATED_KVC_COMPAT static IMP o = 0; /* Backward compatibility hack */ if (o == 0) { o = [NSObject instanceMethodForSelector: @selector(unableToSetNilForKey:)]; } if ([self methodForSelector: @selector(unableToSetNilForKey:)] != o) { [self unableToSetNilForKey: aKey]; return; } #endif [NSException raise: NSInvalidArgumentException format: @"%@ -- %@ 0x%"PRIxPTR": Given nil value to set for key \"%@\"", NSStringFromSelector(_cmd), NSStringFromClass([self class]), (NSUInteger)self, aKey]; } - (void) setValue: (id)anObject forKey: (NSString*)aKey { unsigned size = [aKey length] * 8; char key[size + 1]; BOOL shouldNotify = [[self class] automaticallyNotifiesObserversForKey:aKey]; #ifdef WANT_DEPRECATED_KVC_COMPAT IMP o = [self methodForSelector: @selector(takeValue:forKey:)]; setupCompat(); if (o != takeValue && o != takeValueKVO) { (*o)(self, @selector(takeValue:forKey:), anObject, aKey); return; } #endif [aKey getCString: key maxLength: size + 1 encoding: NSUTF8StringEncoding]; size = strlen(key); if (shouldNotify) { [self willChangeValueForKey: aKey]; } SetValueForKey(self, anObject, key, size); if (shouldNotify) { [self didChangeValueForKey: aKey]; } } - (void) setValue: (id)anObject forKeyPath: (NSString*)aKey { NSRange r = [aKey rangeOfString: @"." options: NSLiteralSearch]; #ifdef WANT_DEPRECATED_KVC_COMPAT IMP o = [self methodForSelector: @selector(takeValue:forKeyPath:)]; setupCompat(); if (o != takePath && o != takePathKVO) { (*o)(self, @selector(takeValue:forKeyPath:), anObject, aKey); return; } #endif if (r.length == 0) { [self setValue: anObject forKey: aKey]; } else { NSString *key = [aKey substringToIndex: r.location]; NSString *path = [aKey substringFromIndex: NSMaxRange(r)]; [[self valueForKey: key] setValue: anObject forKeyPath: path]; } } - (void) setValue: (id)anObject forUndefinedKey: (NSString*)aKey { NSDictionary *dict; NSException *exp; #ifdef WANT_DEPRECATED_KVC_COMPAT static IMP o = 0; /* Backward compatibility hack */ if (o == 0) { o = [NSObject instanceMethodForSelector: @selector(handleTakeValue:forUnboundKey:)]; } if ([self methodForSelector: @selector(handleTakeValue:forUnboundKey:)] != o) { [self handleTakeValue: anObject forUnboundKey: aKey]; return; } #endif dict = [NSDictionary dictionaryWithObjectsAndKeys: (anObject ? (id)anObject : (id)@"(nil)"), @"NSTargetObjectUserInfoKey", (aKey ? (id)aKey : (id)@"(nil)"), @"NSUnknownUserInfoKey", nil]; exp = [NSException exceptionWithName: NSUndefinedKeyException reason: @"Unable to set value for undefined key" userInfo: dict]; [exp raise]; } - (void) setValuesForKeysWithDictionary: (NSDictionary*)aDictionary { NSEnumerator *enumerator; NSString *key; #ifdef WANT_DEPRECATED_KVC_COMPAT static IMP o = 0; /* Backward compatibility hack */ if (o == 0) { o = [NSObject instanceMethodForSelector: @selector(takeValuesFromDictionary:)]; } if ([self methodForSelector: @selector(takeValuesFromDictionary:)] != o) { [self takeValuesFromDictionary: aDictionary]; return; } #endif enumerator = [aDictionary keyEnumerator]; while ((key = [enumerator nextObject]) != nil) { [self setValue: [aDictionary objectForKey: key] forKey: key]; } } - (BOOL) validateValue: (id*)aValue forKey: (NSString*)aKey error: (NSError**)anError { unsigned size; if (aValue == 0 || (size = [aKey length] * 8) == 0) { [NSException raise: NSInvalidArgumentException format: @"nil argument"]; } else { char name[size + 16]; SEL sel; BOOL (*imp)(id,SEL,id*,id*); memcpy(name, "validate", 8); [aKey getCString: &name[8] maxLength: size + 1 encoding: NSUTF8StringEncoding]; size = strlen(&name[8]); memcpy(&name[size + 8], ":error:", 7); name[size + 15] = '\0'; if (islower(name[8])) { name[8] = toupper(name[8]); } sel = sel_getUid(name); if (sel != 0 && [self respondsToSelector: sel] == YES) { imp = (BOOL (*)(id,SEL,id*,id*))[self methodForSelector: sel]; return (*imp)(self, sel, aValue, anError); } } return YES; } - (BOOL) validateValue: (id*)aValue forKeyPath: (NSString*)aKey error: (NSError**)anError { NSRange r = [aKey rangeOfString: @"." options: NSLiteralSearch]; if (r.length == 0) { return [self validateValue: aValue forKey: aKey error: anError]; } else { NSString *key = [aKey substringToIndex: r.location]; NSString *path = [aKey substringFromIndex: NSMaxRange(r)]; return [[self valueForKey: key] validateValue: aValue forKeyPath: path error: anError]; } } - (id) valueForKey: (NSString*)aKey { #if defined(__OBJC2__) return valueForKeyWithCaching(self, aKey); #else unsigned size = [aKey length] * 8; char key[size + 1]; [aKey getCString: key maxLength: size + 1 encoding: NSUTF8StringEncoding]; size = strlen(key); return ValueForKey(self, key, size); #endif } - (id) valueForKeyPath: (NSString*)aKey { NSRange r = [aKey rangeOfString: @"." options: NSLiteralSearch]; if (r.length == 0) { return [self valueForKey: aKey]; } else { NSString *key = [aKey substringToIndex: r.location]; NSString *path = [aKey substringFromIndex: NSMaxRange(r)]; return [[self valueForKey: key] valueForKeyPath: path]; } } - (id) valueForUndefinedKey: (NSString*)aKey { NSDictionary *dict; NSException *exp; NSString *reason; #ifdef WANT_DEPRECATED_KVC_COMPAT static IMP o = 0; /* Backward compatibility hack */ if (o == 0) { o = [NSObject instanceMethodForSelector: @selector(handleQueryWithUnboundKey:)]; } if ([self methodForSelector: @selector(handleQueryWithUnboundKey:)] != o) { return [self handleQueryWithUnboundKey: aKey]; } #endif dict = [NSDictionary dictionaryWithObjectsAndKeys: self, @"NSTargetObjectUserInfoKey", (aKey ? (id)aKey : (id)@"(nil)"), @"NSUnknownUserInfoKey", nil]; reason = [NSString stringWithFormat: @"Unable to find value for key \"%@\" of object %@ (%@)", aKey, self, [self class]]; exp = [NSException exceptionWithName: NSUndefinedKeyException reason: reason userInfo: dict]; [exp raise]; return nil; } #ifdef WANT_DEPRECATED_KVC_COMPAT + (BOOL) useStoredAccessor { return YES; } - (id) storedValueForKey: (NSString*)aKey { unsigned size; if ([[self class] useStoredAccessor] == NO) { return [self valueForKey: aKey]; } size = [aKey length] * 8; if (size > 0) { SEL sel = 0; const char *type = NULL; int off = 0; const char *name; char key[size + 1]; char buf[size + 5]; char lo; char hi; memcpy(buf, "_get", 4); [aKey getCString: key maxLength: size + 1 encoding: NSUTF8StringEncoding]; size = strlen(key); memcpy(&buf[4], key, size); buf[size + 4] = '\0'; lo = buf[4]; hi = islower(lo) ? toupper(lo) : lo; buf[4] = hi; name = buf; // _getKey sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { buf[3] = '_'; buf[4] = lo; name = &buf[3]; // _key sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { sel = 0; } } if (sel == 0) { if ([[self class] accessInstanceVariablesDirectly] == YES) { // _key if (GSObjCFindVariable(self, name, &type, &size, &off) == NO) { name = &buf[4]; // key GSObjCFindVariable(self, name, &type, &size, &off); } } if (type == NULL) { buf[3] = 't'; buf[4] = hi; name = &buf[1]; // getKey sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { buf[4] = lo; name = &buf[4]; // key sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { sel = 0; } } } } if (sel != 0 || type != NULL) { return GSObjCGetVal(self, key, sel, type, size, off); } } [self handleTakeValue: nil forUnboundKey: aKey]; return nil; } - (void) takeStoredValue: (id)anObject forKey: (NSString*)aKey { unsigned size; if ([[self class] useStoredAccessor] == NO) { [self takeValue: anObject forKey: aKey]; return; } size = [aKey length] * 8; if (size > 0) { SEL sel; const char *type; int off; const char *name; char key[size + 1]; char buf[size + 6]; char lo; char hi; memcpy(buf, "_set", 4); [aKey getCString: key maxLength: size + 1 encoding: NSUTF8StringEncoding]; size = strlen(key); memcpy(&buf[4], key, size); buf[size + 4] = '\0'; lo = buf[4]; hi = islower(lo) ? toupper(lo) : lo; buf[4] = hi; buf[size + 4] = ':'; buf[size + 5] = '\0'; name = buf; // _setKey: type = NULL; off = 0; sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { sel = 0; if ([[self class] accessInstanceVariablesDirectly] == YES) { buf[size + 4] = '\0'; buf[4] = lo; buf[3] = '_'; name = &buf[3]; // _key if (GSObjCFindVariable(self, name, &type, &size, &off) == NO) { name = &buf[4]; // key GSObjCFindVariable(self, name, &type, &size, &off); } } if (type == NULL) { buf[size + 4] = ':'; buf[4] = hi; buf[3] = 't'; name = &buf[1]; // setKey: sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { sel = 0; } } } if (sel != 0 || type != NULL) { GSObjCSetVal(self, key, anObject, sel, type, size, off); return; } } [self handleTakeValue: anObject forUnboundKey: aKey]; } - (void) takeStoredValuesFromDictionary: (NSDictionary*)aDictionary { NSEnumerator *enumerator = [aDictionary keyEnumerator]; NSNull *null = [NSNull null]; NSString *key; while ((key = [enumerator nextObject]) != nil) { id obj = [aDictionary objectForKey: key]; if (obj == null) { obj = nil; } [self takeStoredValue: obj forKey: key]; } } - (id) handleQueryWithUnboundKey: (NSString*)aKey { NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: self, @"NSTargetObjectUserInfoKey", (aKey ? (id)aKey : (id)@"(nil)"), @"NSUnknownUserInfoKey", nil]; NSException *exp = [NSException exceptionWithName: NSUndefinedKeyException reason: @"Unable to find value for key" userInfo: dict]; GSOnceMLog(@"This method is deprecated, use -valueForUndefinedKey:"); [exp raise]; return nil; } - (void) handleTakeValue: (id)anObject forUnboundKey: (NSString*)aKey { NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: (anObject ? (id)anObject : (id)@"(nil)"), @"NSTargetObjectUserInfoKey", (aKey ? (id)aKey : (id)@"(nil)"), @"NSUnknownUserInfoKey", nil]; NSException *exp = [NSException exceptionWithName: NSUndefinedKeyException reason: @"Unable to set value for key" userInfo: dict]; GSOnceMLog(@"This method is deprecated, use -setValue:forUndefinedKey:"); [exp raise]; } - (void) takeValue: (id)anObject forKey: (NSString*)aKey { SEL sel = 0; const char *type = 0; int off = 0; unsigned size = [aKey length] * 8; char key[size + 1]; GSOnceMLog(@"This method is deprecated, use -setValue:forKey:"); [aKey getCString: key maxLength: size + 1 encoding: NSUTF8StringEncoding]; size = strlen(key); if (size > 0) { const char *name; char buf[size + 6]; char lo; char hi; memcpy(buf, "_set", 4); memcpy(&buf[4], key, size); lo = buf[4]; hi = islower(lo) ? toupper(lo) : lo; buf[4] = hi; buf[size + 4] = ':'; buf[size + 5] = '\0'; name = &buf[1]; // setKey: type = NULL; sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { name = buf; // _setKey: sel = sel_getUid(name); if (sel == 0 || [self respondsToSelector: sel] == NO) { sel = 0; if ([[self class] accessInstanceVariablesDirectly] == YES) { buf[size + 4] = '\0'; buf[3] = '_'; buf[4] = lo; name = &buf[4]; // key if (GSObjCFindVariable(self, name, &type, &size, &off) == NO) { name = &buf[3]; // _key GSObjCFindVariable(self, name, &type, &size, &off); } } } } } GSObjCSetVal(self, key, anObject, sel, type, size, off); } - (void) takeValue: (id)anObject forKeyPath: (NSString*)aKey { NSRange r = [aKey rangeOfString: @"." options: NSLiteralSearch]; GSOnceMLog(@"This method is deprecated, use -setValue:forKeyPath:"); if (r.length == 0) { [self takeValue: anObject forKey: aKey]; } else { NSString *key = [aKey substringToIndex: r.location]; NSString *path = [aKey substringFromIndex: NSMaxRange(r)]; [[self valueForKey: key] takeValue: anObject forKeyPath: path]; } } - (void) takeValuesFromDictionary: (NSDictionary*)aDictionary { NSEnumerator *enumerator = [aDictionary keyEnumerator]; NSNull *null = [NSNull null]; NSString *key; GSOnceMLog(@"This method is deprecated, use -setValuesForKeysWithDictionary:"); while ((key = [enumerator nextObject]) != nil) { id obj = [aDictionary objectForKey: key]; if (obj == null) { obj = nil; } [self takeValue: obj forKey: key]; } } - (void) unableToSetNilForKey: (NSString*)aKey { GSOnceMLog(@"This method is deprecated, use -setNilValueForKey:"); [NSException raise: NSInvalidArgumentException format: @"%@ -- %@ 0x%"PRIxPTR": Given nil value to set for key \"%@\"", NSStringFromSelector(_cmd), NSStringFromClass([self class]), (NSUInteger)self, aKey]; } - (NSDictionary*) valuesForKeys: (NSArray*)keys { NSMutableDictionary *dict; NSNull *null = [NSNull null]; unsigned count = [keys count]; unsigned pos; GSOnceMLog(@"This method is deprecated, use -dictionaryWithValuesForKeys:"); dict = [NSMutableDictionary dictionaryWithCapacity: count]; for (pos = 0; pos < count; pos++) { NSString *key = [keys objectAtIndex: pos]; id val = [self valueForKey: key]; if (val == nil) { val = null; } [dict setObject: val forKey: key]; } return AUTORELEASE([dict copy]); } #endif @end