Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -36,10 +36,11 @@ OFMutableSet.m \ OFMutableString.m \ OFNull.m \ OFNumber.m \ OFObject.m \ + OFObject+KeyValueCoding.m \ OFObject+Serialization.m \ OFOptionsParser.m \ ${OFPROCESS_M} \ OFRIPEMD160Hash.m \ OFRunLoop.m \ @@ -114,10 +115,11 @@ INCLUDES := ${SRCS:.m=.h} \ OFCollection.h \ OFHash.h \ OFJSONRepresentation.h \ + OFKeyValueCoding.h \ OFLocking.h \ OFMessagePackRepresentation.h \ OFSerialization.h \ OFTLSSocket.h \ ObjFW.h \ ADDED src/OFKeyValueCoding.h Index: src/OFKeyValueCoding.h ================================================================== --- src/OFKeyValueCoding.h +++ src/OFKeyValueCoding.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +@class OFString; + +/*! + * @protocol OFKeyValueCoding OFKeyValueCoding.h ObjFW/OFKeyValueCoding.h + * + * @brief A protocol for Key Value Coding. + * + * Key Value Coding makes it possible to access properties dynamically using + * the interface described by this protocol. + */ +@protocol OFKeyValueCoding +/*! + * @brief Return the value for the specified key + * + * @param key The key of the value to return + * @return The value for the specified key + */ +- (id)valueForKey: (OFString*)key; + +/*! + * @brief This is called by @ref valueForKey: if the specified key does not + * exist. + * + * By default, this throws an @ref OFUndefinedKeyException. + * + * @param key The undefined key of the value to return + * @return The value for the specified undefined key + */ +- (id)valueForUndefinedKey: (OFString*)key; + +/*! + * @brief Set the value for the specified key + * + * @param value The value for the specified key + * @param key The key of the value to set + */ +- (void)setValue: (id)value + forKey: (OFString*)key; + +/*! + * @brief This is called by @ref setValue:forKey: if the specified key does not + * exist. + * + * By default, this throws an @ref OFUndefinedKeyException. + * + * @param value The value for the specified undefined key + * @param key The undefined key of the value to set + */ +- (void)setValue: (id)value + forUndefinedKey: (OFString*)key; +@end ADDED src/OFObject+KeyValueCoding.h Index: src/OFObject+KeyValueCoding.h ================================================================== --- src/OFObject+KeyValueCoding.h +++ src/OFObject+KeyValueCoding.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFObject.h" +#import "OFKeyValueCoding.h" + +OF_ASSUME_NONNULL_BEGIN + +#ifdef __cplusplus +extern "C" { +#endif +extern int _OFObject_KeyValueCoding_reference; +#ifdef __cplusplus +} +#endif + +@interface OFObject (KeyValueCoding) +@end + +OF_ASSUME_NONNULL_END ADDED src/OFObject+KeyValueCoding.m Index: src/OFObject+KeyValueCoding.m ================================================================== --- src/OFObject+KeyValueCoding.m +++ src/OFObject+KeyValueCoding.m @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include + +#import "OFObject.h" +#import "OFObject+KeyValueCoding.h" +#import "OFString.h" +#import "OFNull.h" + +#import "OFOutOfMemoryException.h" +#import "OFUndefinedKeyException.h" + +int _OFObject_KeyValueCoding_reference; + +static bool +checkTypeEncoding(const char *typeEncoding, char returnType, ...) +{ + va_list args; + char type; + + if (typeEncoding == NULL) + return false; + + if (*typeEncoding++ != returnType) + return false; + + while (*typeEncoding >= '0' && *typeEncoding <= '9') + typeEncoding++; + + va_start(args, returnType); + + while ((type = va_arg(args, int)) != 0) { + if (*typeEncoding++ != type) + return false; + + while (*typeEncoding >= '0' && *typeEncoding <= '9') + typeEncoding++; + } + + if (*typeEncoding != '\0') + return false; + + return true; +} + +@implementation OFObject (KeyValueCoding) +- (id)valueForKey: (OFString*)key +{ + SEL selector = sel_registerName([key UTF8String]); + const char *typeEncoding = [self typeEncodingForSelector: selector]; + + if (!checkTypeEncoding(typeEncoding, '@', '@', ':', 0)) + return [self valueForUndefinedKey: key]; + + return [self performSelector: selector]; +} + +- (id)valueForUndefinedKey: (OFString*)key +{ + @throw [OFUndefinedKeyException exceptionWithObject: self + key: key]; +} + +- (void)setValue: (id)value + forKey: (OFString*)key +{ + char *name; + size_t keyLength; + SEL selector; + const char *typeEncoding; + id (*setter)(id, SEL, id); + + keyLength = [key UTF8StringLength]; + + if (keyLength < 1) { + [self setValue: value + forUndefinedKey: key]; + return; + } + + if ((name = malloc(keyLength + 5)) == NULL) + @throw [OFOutOfMemoryException + exceptionWithRequestedSize: keyLength + 5]; + + memcpy(name, "set", 3); + memcpy(name + 3, [key UTF8String], keyLength); + memcpy(name + keyLength + 3, ":", 2); + + name[3] = toupper(name[3]); + + selector = sel_registerName(name); + + free(name); + + typeEncoding = [self typeEncodingForSelector: selector]; + + if (!checkTypeEncoding(typeEncoding, 'v', '@', ':', '@', 0)) { + [self setValue: value + forUndefinedKey: key]; + return; + } + + setter = (id(*)(id, SEL, id))[self methodForSelector: selector]; + setter(self, selector, value); +} + +- (void)setValue: (id)value + forUndefinedKey: (OFString*)key +{ + @throw [OFUndefinedKeyException exceptionWithObject: self + key: key + value: value]; +} +@end Index: src/OFObject.h ================================================================== --- src/OFObject.h +++ src/OFObject.h @@ -965,6 +965,7 @@ } #endif OF_ASSUME_NONNULL_END +#import "OFObject+KeyValueCoding.h" #import "OFObject+Serialization.h" Index: src/OFObject.m ================================================================== --- src/OFObject.m +++ src/OFObject.m @@ -209,10 +209,11 @@ /* References for static linking */ void _references_to_categories_of_OFObject(void) { + _OFObject_KeyValueCoding_reference = 1; _OFObject_Serialization_reference = 1; } @implementation OFObject + (void)load Index: src/exceptions/Makefile ================================================================== --- src/exceptions/Makefile +++ src/exceptions/Makefile @@ -39,10 +39,11 @@ OFStatItemFailedException.m \ OFStillLockedException.m \ OFTruncatedDataException.m \ OFUnboundNamespaceException.m \ OFUnboundPrefixException.m \ + OFUndefinedKeyException.m \ OFUnknownXMLEntityException.m \ OFUnlockFailedException.m \ OFUnsupportedProtocolException.m \ OFUnsupportedVersionException.m \ OFWriteFailedException.m \ ADDED src/exceptions/OFUndefinedKeyException.h Index: src/exceptions/OFUndefinedKeyException.h ================================================================== --- src/exceptions/OFUndefinedKeyException.h +++ src/exceptions/OFUndefinedKeyException.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFException.h" + +/*! + * @class OFUndefinedKeyException \ + * OFUndefinedKeyException.h ObjFW/OFUndefinedKeyException.h + * + * @brief An exception indicating that a key for Key Value Coding is undefined. + */ +@interface OFUndefinedKeyException: OFException +{ + id _object; + OFString *_key; + id _value; +} + +/*! + * The object on which the key is undefined. + */ +@property (readonly, retain) id object; + +/*! + * The key which is undefined. + */ +@property (readonly, copy) OFString *key; + +/*! + * The value for the undefined key + */ +@property (readonly, retain) id value; + +/*! + * @brief Creates a new, autoreleased undefined key exception. + * + * @param object The object on which the key is undefined + * @param key The key which is undefined + * + * @return A new, autoreleased undefined key exception + */ ++ (instancetype)exceptionWithObject: (id)object + key: (OFString*)key; + +/*! + * @brief Creates a new, autoreleased undefined key exception. + * + * @param object The object on which the key is undefined + * @param key The key which is undefined + * @param value The value for the undefined key + * + * @return A new, autoreleased undefined key exception + */ ++ (instancetype)exceptionWithObject: (id)object + key: (OFString*)key + value: (id)value; + +/*! + * @brief Initializes an already allocated undefined key exception. + * + * @param object The object on which the key is undefined + * @param key The key which is undefined + * + * @return An initialized undefined key exception + */ +- initWithObject: (id)object + key: (OFString*)key; + +/*! + * @brief Initializes an already allocated undefined key exception. + * + * @param object The object on which the key is undefined + * @param key The key which is undefined + * @param value The value for the undefined key + * + * @return An initialized undefined key exception + */ +- initWithObject: (id)object + key: (OFString*)key + value: (id)value; +@end ADDED src/exceptions/OFUndefinedKeyException.m Index: src/exceptions/OFUndefinedKeyException.m ================================================================== --- src/exceptions/OFUndefinedKeyException.m +++ src/exceptions/OFUndefinedKeyException.m @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFUndefinedKeyException.h" +#import "OFString.h" + +@implementation OFUndefinedKeyException +@synthesize object = _object, key = _key, value = _value; + ++ (instancetype)exceptionWithObject: (id)object + key: (OFString*)key +{ + return [[[self alloc] initWithObject: object + key: key] autorelease]; +} + ++ (instancetype)exceptionWithObject: (id)object + key: (OFString*)key + value: (id)value +{ + return [[[self alloc] initWithObject: object + key: key + value: value] autorelease]; +} + +- init +{ + OF_INVALID_INIT_METHOD +} + +- initWithObject: (id)object + key: (OFString*)key +{ + self = [super init]; + + @try { + _object = [object retain]; + _key = [key copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- initWithObject: (id)object + key: (OFString*)key + value: (id)value +{ + self = [super init]; + + @try { + _object = [object retain]; + _key = [key copy]; + _value = [value retain]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_object release]; + [_key release]; + [_value release]; + + [super dealloc]; +} + +- (OFString*)description +{ + return [OFString stringWithFormat: + @"The key \"%@\" is undefined for an object of type %@!", + _key, [_object className]]; +} +@end