From fe0950066553a82744d13a5078ba12a7fb852679 Mon Sep 17 00:00:00 2001 From: Richard Frith-MacDonald Date: Mon, 27 Nov 2000 22:26:08 +0000 Subject: [PATCH] Key value coding and class description support added. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@8219 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 9 + Headers/gnustep/base/Foundation.h | 2 + Headers/gnustep/base/NSClassDescription.h | 61 ++++ Headers/gnustep/base/NSKeyValueCoding.h | 55 ++++ Source/GNUmakefile | 3 + Source/NSClassDescription.m | 143 +++++++++ Source/NSObject.m | 370 ++++++++++++++++++++++ Source/externs.m | 7 + 8 files changed, 650 insertions(+) create mode 100644 Headers/gnustep/base/NSClassDescription.h create mode 100644 Headers/gnustep/base/NSKeyValueCoding.h create mode 100644 Source/NSClassDescription.m diff --git a/ChangeLog b/ChangeLog index 3ccd6c118..4b00edd54 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2000-11-27 Richard Frith-Macdonald + + * Source/NSClassDescription.m: New MacOS-X class added. + * Headers/Foundation/NSClassDescription.m: New MacOS-X class added. + * Headers/Foundation/NSKeyvalueCoding.m: New MacOS-X protocol added. + * Source/NSObject.m: New MacOS-X key value coding methods added. + Only objects currently supported - needs work to support scalar + types too! + 2000-11-23 Mirko Viviani * Source/NSBundle.m ([NSBundle +_addFrameworkFromClass:]): fixed an diff --git a/Headers/gnustep/base/Foundation.h b/Headers/gnustep/base/Foundation.h index d9c2996fd..d9149da87 100644 --- a/Headers/gnustep/base/Foundation.h +++ b/Headers/gnustep/base/Foundation.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ #include #include #include +#include #include #include #include diff --git a/Headers/gnustep/base/NSClassDescription.h b/Headers/gnustep/base/NSClassDescription.h new file mode 100644 index 000000000..2c6e011d4 --- /dev/null +++ b/Headers/gnustep/base/NSClassDescription.h @@ -0,0 +1,61 @@ +/* Interface for NSClassDescription for GNUStep + Copyright (C) 2000 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: 2000 + + 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 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; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + */ + +#ifndef __NSClassDescription_h_GNUSTEP_BASE_INCLUDE +#define __NSClassDescription_h_GNUSTEP_BASE_INCLUDE + +#include +#include + +@class NSArray; +@class NSDictionary; +@class NSString; + +GS_EXPORT NSString* const NSClassDescriptionNeededForClassNotification; + +@interface NSClassDescription : NSObject + ++ (NSClassDescription*) classDescriptionForClass: (Class)aClass; ++ (void) invalidateClassDescriptionCache; ++ (void) registerClassDescription: (NSClassDescription*)aDescription + forClass: (Class)aClass; + +- (NSArray*) attributeKeys; +- (NSString*) inverseForRelationshipKey: (NSString*)aKey; +- (NSArray*) toManyRelationshipKeys; +- (NSArray*) toOneRelationshipKeys; + +@end + +@interface NSObject (NSClassDescriptionPrimitives) + +- (NSArray*) attributeKeys; +- (NSClassDescription*) classDescription; +- (NSString*) inverseForRelationshipKey: (NSString*)aKey; +- (NSArray*) toManyRelationshipKeys; +- (NSArray*) toOneRelationshipKeys; + +@end + +#endif + diff --git a/Headers/gnustep/base/NSKeyValueCoding.h b/Headers/gnustep/base/NSKeyValueCoding.h new file mode 100644 index 000000000..74c21de09 --- /dev/null +++ b/Headers/gnustep/base/NSKeyValueCoding.h @@ -0,0 +1,55 @@ + +/* Interface for NSKeyvalueCoding for GNUStep + Copyright (C) 2000 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: 2000 + + 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 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; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + */ + +#ifndef __NSKeyValueCoding_h_GNUSTEP_BASE_INCLUDE +#define __NSKeyValueCoding_h_GNUSTEP_BASE_INCLUDE + +#include + +@class NSArray; +@class NSDictionary; +@class NSString; + + +@interface NSObject (NSKeyValueCoding) + ++ (BOOL) accessInstanceVariablesDirectly; ++ (BOOL) useStoredAccessor; + +- (id) handleQueryWithUnboundKey: (NSString*)aKey; +- (void) handleTakeValue: (id)anObject forUnboundKey: (NSString*)aKey; +- (id) storedValueForKey: (NSString*)aKey; +- (void) takeStoredValue: (id)anObject forKey: (NSString*)aKey; +- (void) takeValue: (id)anObject forKey: (NSString*)aKey; +- (void) takeValue: (id)anObject forKeyPath: (NSString*)aKey; +- (void) takeValuesFromDictionary: (NSDictionary*)aDictionary; +- (void) unableToSetNilForKey: (NSString*)aKey; +- (id) valueForKey: (NSString*)aKey; +- (id) valueForKeyPath: (NSString*)aKey; +- (NSDictionary*) valuesForKeys: (NSArray*)keys; + +@end + +#endif + diff --git a/Source/GNUmakefile b/Source/GNUmakefile index c12ac3ff9..365884e52 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -157,6 +157,7 @@ NSBundle.m \ NSCalendarDate.m \ NSCallBacks.m \ NSCharacterSet.m \ +NSClassDescription.m \ NSCoder.m \ NSCopyObject.m \ NSConcreteValue.m \ @@ -265,6 +266,7 @@ NSBitmapCharSet.h \ NSBundle.h \ NSByteOrder.h \ NSCharacterSet.h \ +NSClassDescription.h \ NSCoder.h \ NSConcreteNumber.h \ NSConcreteValue.h \ @@ -291,6 +293,7 @@ NSGSet.h \ NSHashTable.h \ NSHost.h \ NSInvocation.h \ +NSKeyValueEncoding.h \ NSLock.h \ NSMapTable.h \ NSMethodSignature.h \ diff --git a/Source/NSClassDescription.m b/Source/NSClassDescription.m new file mode 100644 index 000000000..2fb64982b --- /dev/null +++ b/Source/NSClassDescription.m @@ -0,0 +1,143 @@ + +/* NSClassDescription + Copyright (C) 2000 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: 2000 + + 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 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; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. +*/ + +#include +#include +#include +#include + + +@implementation NSClassDescription + +static NSRecursiveLock *mapLock = nil; +static NSMapTable *classMap; + ++ (NSClassDescription*) classDescriptionForClass: (Class)aClass +{ + NSClassDescription *description; + + [mapLock lock]; + description = NSMapGet(classMap, aClass); + if (description == nil) + { + NSNotificationCenter *nc; + + nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName: NSClassDescriptionNeededForClassNotification + object: aClass]; + description = NSMapGet(classMap, aClass); + } + RETAIN(description); + [mapLock unlock]; + + return AUTORELEASE(description); +} + ++ (void) initialize +{ + if (self == [NSClassDescription class]) + { + classMap = NSCreateMapTable(NSObjectMapKeyCallBacks, + NSObjectMapValueCallBacks, 100); + mapLock = [NSRecursiveLock new]; + } +} + ++ (void) invalidateClassDescriptionCache +{ + [mapLock lock]; + NSResetMapTable(classMap); + [mapLock unlock]; +} + ++ (void) registerClassDescription: (NSClassDescription*)aDescription + forClass: (Class)aClass +{ + if (aDescription != nil && aClass != 0) + { + [mapLock lock]; + NSMapInsert(classMap, aClass, aDescription); + [mapLock unlock]; + } +} + +- (NSArray*) attributeKeys +{ + return nil; +} + +- (NSString*) inverseForRelationshipKey: (NSString*)aKey +{ + return nil; +} + +- (NSArray*) toManyRelationshipKeys +{ + return nil; +} + +- (NSArray*) toOneRelationshipKeys +{ + return nil; +} + +@end + + + +@implementation NSObject(ClassDescriptionForwards) + +static Class NSClassDescriptionClass = 0; + +- (NSArray*) attributeKeys +{ + return [[self classDescription] attributeKeys]; +} + +- (NSClassDescription*) classDescription +{ + if (NSClassDescriptionClass == 0) + { + NSClassDescriptionClass = [NSClassDescription class]; + } + return [NSClassDescriptionClass classDescriptionForClass: [self class]]; +} + +- (NSString*) inverseForRelationshipKey: (NSString*)aKey +{ + return [[self classDescription] inverseForRelationshipKey: aKey]; +} + +- (NSArray*) toManyRelationshipKeys +{ + return [[self classDescription] toManyRelationshipKeys]; +} + +- (NSArray*) toOneRelationshipKeys +{ + return [[self classDescription] toOneRelationshipKeys]; +} + +@end + diff --git a/Source/NSObject.m b/Source/NSObject.m index 97a958a43..6bb5c049e 100644 --- a/Source/NSObject.m +++ b/Source/NSObject.m @@ -1276,6 +1276,376 @@ static BOOL deallocNotifications = NO; @end + +#include +#include + + +static BOOL +GSGetValue(id obj, NSString *iVarName, id *data) +{ + const char *name = [iVarName cString]; + Class class; + struct objc_ivar_list *ivars; + struct objc_ivar *ivar = 0; + int offset; + const char *type; + unsigned int size; + + class = [obj class]; + while (class != nil && ivar == 0) + { + ivars = class->ivars; + class = class->super_class; + if (ivars != 0) + { + int i; + + for (i = 0; i < ivars->ivar_count; i++) + { + if (strcmp(ivars->ivar_list[i].ivar_name, name) == 0) + { + ivar = &ivars->ivar_list[i]; + break; + } + } + } + } + if (ivar == 0) + { + return NO; + } + + offset = ivar->ivar_offset; + type = ivar->ivar_type; + size = objc_sizeof_type(type); + memcpy(data, ((void*)obj) + offset, size); + return YES; +} + +static BOOL +GSSetValue(id obj, NSString *iVarName, id *data) +{ + const char *name = [iVarName cString]; + Class class; + struct objc_ivar_list *ivars; + struct objc_ivar *ivar = 0; + int offset; + const char *type; + unsigned int size; + + class = [obj class]; + while (class != nil && ivar == 0) + { + ivars = class->ivars; + class = class->super_class; + if (ivars != 0) + { + int i; + + for (i = 0; i < ivars->ivar_count; i++) + { + if (strcmp(ivars->ivar_list[i].ivar_name, name) == 0) + { + ivar = &ivars->ivar_list[i]; + break; + } + } + } + } + if (ivar == 0) + { + return NO; + } + + offset = ivar->ivar_offset; + type = ivar->ivar_type; + size = objc_sizeof_type(type); + memcpy(((void*)obj) + offset, data, size); + return YES; +} + +@implementation NSObject (KeyValueCoding) + ++ (BOOL) accessInstanceVariablesDirectly +{ + return YES; +} + ++ (BOOL) useStoredAccessor +{ + return YES; +} + +- (id) handleQueryWithUnboundKey: (NSString*)aKey +{ + return nil; +} + +- (void) handleTakeValue: (id)anObject forUnboundKey: (NSString*)aKey +{ + [NSException raise: NSGenericException + format: @"Unable to find %@ in %@", aKey, anObject]; +} + +- (id) storedValueForKey: (NSString*)aKey +{ + SEL sel; + + if ([[self class] useStoredAccessor] == NO) + { + return [self valueForKey: aKey]; + } + + sel = NSSelectorFromString([NSString stringWithFormat: @"_get%@", + [aKey capitalizedString]]); + if ([self respondsToSelector: sel] == YES) + { + return [self performSelector: sel]; + } + sel = NSSelectorFromString([NSString stringWithFormat: @"_%@", aKey]); + if ([self respondsToSelector: sel] == YES) + { + return [self performSelector: sel]; + } + + if ([[self class] accessInstanceVariablesDirectly] == YES) + { + id v; + + if (GSGetValue(self, [NSString stringWithFormat: @"_%@", aKey], + (void*)&v) == YES) + { + return v; + } + if (GSGetValue(self, aKey, (void*)&v) == YES) + { + return v; + } + } + + sel = NSSelectorFromString([NSString stringWithFormat: @"get%@", + [aKey capitalizedString]]); + if ([self respondsToSelector: sel] == YES) + { + return [self performSelector: sel]; + } + sel = NSSelectorFromString(aKey); + if ([self respondsToSelector: sel] == YES) + { + return [self performSelector: sel]; + } + + [self handleTakeValue: nil forUnboundKey: aKey]; + return nil; +} + +- (void) takeStoredValue: (id)anObject forKey: (NSString*)aKey +{ + SEL sel; + + if ([[self class] useStoredAccessor] == NO) + { + [self takeValue: anObject forKey: aKey]; + return; + } + + sel = NSSelectorFromString([NSString stringWithFormat: @"_set%@:", + [aKey capitalizedString]]); + if ([self respondsToSelector: sel] == YES) + { + [self performSelector: sel withObject: anObject]; + return; + } + + if ([[self class] accessInstanceVariablesDirectly] == YES) + { + if (GSSetValue(self, [NSString stringWithFormat: @"_%@", aKey], + (void*)&anObject) == YES) + { + return; + } + if (GSSetValue(self, aKey, (void*)&anObject) == YES) + { + return; + } + } + + sel = NSSelectorFromString([NSString stringWithFormat: @"set%@:", + [aKey capitalizedString]]); + if ([self respondsToSelector: sel] == YES) + { + [self performSelector: sel withObject: anObject]; + return; + } + + [self handleTakeValue: anObject forUnboundKey: aKey]; +} + +- (void) takeValue: (id)anObject forKey: (NSString*)aKey +{ + SEL sel; + + sel = NSSelectorFromString([NSString stringWithFormat: @"set%@:", + [aKey capitalizedString]]); + if ([self respondsToSelector: sel] == YES) + { + [self performSelector: sel withObject: anObject]; + return; + } + + sel = NSSelectorFromString([NSString stringWithFormat: @"_set%@:", + [aKey capitalizedString]]); + if ([self respondsToSelector: sel] == YES) + { + [self performSelector: sel withObject: anObject]; + return; + } + + if ([[self class] accessInstanceVariablesDirectly] == YES) + { + if (GSSetValue(self, [NSString stringWithFormat: @"_%@", aKey], + (void*)&anObject) == YES) + { + return; + } + if (GSSetValue(self, aKey, (void*)&anObject) == YES) + { + return; + } + } + + [self handleTakeValue: anObject forUnboundKey: aKey]; +} + +- (void) takeValue: (id)anObject forKeyPath: (NSString*)aKey +{ + NSArray *keys = [aKey componentsSeparatedByString: @"."]; + id obj = self; + unsigned count = [keys count]; + unsigned pos; + + for (pos = 0; pos + 1 < count; pos++) + { + obj = [obj valueForKey: [keys objectAtIndex: pos]]; + } + if (pos < count) + { + [obj takeValue: anObject forKey: [keys objectAtIndex: pos]]; + } +} + +- (void) takeValuesFromDictionary: (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 takeValue: obj forKey: key]; + } +} + +- (void) unableToSetNilForKey: (NSString*)aKey +{ + [NSException raise: NSInvalidArgumentException + format: @"Given nil value to set for key"]; +} + +- (id) valueForKey: (NSString*)aKey +{ + SEL sel; + + sel = NSSelectorFromString([NSString stringWithFormat: @"get%@", + [aKey capitalizedString]]); + if ([self respondsToSelector: sel] == YES) + { + return [self performSelector: sel]; + } + sel = NSSelectorFromString(aKey); + if ([self respondsToSelector: sel] == YES) + { + return [self performSelector: sel]; + } + + sel = NSSelectorFromString([NSString stringWithFormat: @"_get%@", + [aKey capitalizedString]]); + if ([self respondsToSelector: sel] == YES) + { + return [self performSelector: sel]; + } + sel = NSSelectorFromString([NSString stringWithFormat: @"_%@", aKey]); + if ([self respondsToSelector: sel] == YES) + { + return [self performSelector: sel]; + } + + if ([[self class] accessInstanceVariablesDirectly] == YES) + { + id v; + + if (GSGetValue(self, [NSString stringWithFormat: @"_%@", aKey], + (void*)&v) == YES) + { + return v; + } + if (GSGetValue(self, aKey, (void*)&v) == YES) + { + return v; + } + } + + [self handleTakeValue: nil forUnboundKey: aKey]; + return nil; +} + +- (id) valueForKeyPath: (NSString*)aKey +{ + NSArray *keys = [aKey componentsSeparatedByString: @"."]; + id obj = self; + unsigned count = [keys count]; + unsigned pos; + + for (pos = 0; pos < count; pos++) + { + obj = [obj valueForKey: [keys objectAtIndex: pos]]; + } + return obj; +} + +- (NSDictionary*) valuesForKeys: (NSArray*)keys +{ + NSMutableDictionary *dict; + NSNull *null = [NSNull null]; + unsigned count = [keys count]; + unsigned pos; + + 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]); +} + +@end + + + @implementation NSObject (GNUstep) /* GNU Object class compatibility */ diff --git a/Source/externs.m b/Source/externs.m index 9ee2d0fdd..d573e3a67 100644 --- a/Source/externs.m +++ b/Source/externs.m @@ -290,6 +290,9 @@ NSString *GSHTTPPropertyProxyHostKey; NSString *GSHTTPPropertyProxyPortKey; +/* Class description notification */ +NSString *NSClassDescriptionNeededForClassNotification; + /* * Setup function called when NSString is initialised. @@ -514,6 +517,10 @@ GSBuildStrings() = [[SClass alloc] initWithCString: "GSHTTPPropertyProxyHostKey"]; GSHTTPPropertyProxyPortKey = [[SClass alloc] initWithCString: "GSHTTPPropertyProxyPortKey"]; + + NSClassDescriptionNeededForClassNotification + = [[SClass alloc] initWithCString: + "NSClassDescriptionNeededForClassNotification"]; } }