/*
* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
* Jonathan Schleifer <js@heap.zone>
*
* 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 <stdlib.h>
#import "OFObject.h"
#import "OFObject+KeyValueCoding.h"
#import "OFString.h"
#import "OFNumber.h"
#import "OFInvalidArgumentException.h"
#import "OFOutOfMemoryException.h"
#import "OFUndefinedKeyException.h"
int _OFObject_KeyValueCoding_reference;
static char OF_INLINE
nextType(const char **typeEncoding)
{
char ret = *(*typeEncoding)++;
while (**typeEncoding >= '0' && **typeEncoding <= '9')
(*typeEncoding)++;
return ret;
}
@implementation OFObject (KeyValueCoding)
- (id)valueForKey: (OFString *)key
{
SEL selector = sel_registerName([key UTF8String]);
const char *typeEncoding = [self typeEncodingForSelector: selector];
id ret;
if (typeEncoding == NULL) {
size_t keyLength;
char *name;
if ((keyLength = [key UTF8StringLength]) < 1)
return [self valueForUndefinedKey: key];
if ((name = malloc(keyLength + 3)) == NULL)
@throw [OFOutOfMemoryException
exceptionWithRequestedSize: keyLength + 3];
@try {
memcpy(name, "is", 2);
memcpy(name + 2, [key UTF8String], keyLength);
name[keyLength + 2] = '\0';
name[2] = of_ascii_toupper(name[2]);
selector = sel_registerName(name);
} @finally {
free(name);
}
typeEncoding = [self typeEncodingForSelector: selector];
if (typeEncoding == NULL ||
*typeEncoding == '@' || *typeEncoding == '#')
return [self valueForUndefinedKey: key];
}
switch (nextType(&typeEncoding)) {
case '@':
case '#':
ret = [self performSelector: selector];
break;
#define CASE(encoding, type, method) \
case encoding: \
{ \
type (*getter)(id, SEL) = (type (*)(id, SEL)) \
[self methodForSelector: selector]; \
ret = [OFNumber method getter(self, selector)]; \
} \
break;
CASE('B', bool, numberWithBool:)
CASE('c', char, numberWithChar:)
CASE('s', short, numberWithShort:)
CASE('i', int, numberWithInt:)
CASE('l', long, numberWithLong:)
CASE('q', long long, numberWithLongLong:)
CASE('C', unsigned char, numberWithUnsignedChar:)
CASE('S', unsigned short, numberWithUnsignedShort:)
CASE('I', unsigned int, numberWithUnsignedInt:)
CASE('L', unsigned long, numberWithUnsignedLong:)
CASE('Q', unsigned long long, numberWithUnsignedLongLong:)
CASE('f', float, numberWithFloat:)
CASE('d', double, numberWithDouble:)
#undef CASE
default:
return [self valueForUndefinedKey: key];
}
if (nextType(&typeEncoding) != '@' || nextType(&typeEncoding) != ':' ||
*typeEncoding != 0)
return [self valueForUndefinedKey: key];
return ret;
}
- (id)valueForUndefinedKey: (OFString *)key
{
@throw [OFUndefinedKeyException exceptionWithObject: self
key: key];
}
- (void)setValue: (id)value
forKey: (OFString *)key
{
size_t keyLength;
char *name;
SEL selector;
const char *typeEncoding;
char valueType;
if ((keyLength = [key UTF8StringLength]) < 1) {
[self setValue: value
forUndefinedKey: key];
return;
}
if ((name = malloc(keyLength + 5)) == NULL)
@throw [OFOutOfMemoryException
exceptionWithRequestedSize: keyLength + 5];
@try {
memcpy(name, "set", 3);
memcpy(name + 3, [key UTF8String], keyLength);
memcpy(name + keyLength + 3, ":", 2);
name[3] = of_ascii_toupper(name[3]);
selector = sel_registerName(name);
} @finally {
free(name);
}
typeEncoding = [self typeEncodingForSelector: selector];
if (typeEncoding == NULL || nextType(&typeEncoding) != 'v' ||
nextType(&typeEncoding) != '@' || nextType(&typeEncoding) != ':') {
[self setValue: value
forUndefinedKey: key];
return;
}
valueType = nextType(&typeEncoding);
if (*typeEncoding != 0) {
[self setValue: value
forUndefinedKey: key];
return;
}
if (valueType != '@' && valueType != '#' && value == nil) {
[self setNilValueForKey: key];
return;
}
switch (valueType) {
case '@':
case '#':
{
void (*setter)(id, SEL, id) = (void (*)(id, SEL, id))
[self methodForSelector: selector];
setter(self, selector, value);
}
break;
#define CASE(encoding, type, method) \
case encoding: \
{ \
void (*setter)(id, SEL, type) = \
(void (*)(id, SEL, type)) \
[self methodForSelector: selector]; \
setter(self, selector, [value method]); \
} \
break;
CASE('B', bool, boolValue)
CASE('c', char, charValue)
CASE('s', short, shortValue)
CASE('i', int, intValue)
CASE('l', long, longValue)
CASE('q', long long, longLongValue)
CASE('C', unsigned char, unsignedCharValue)
CASE('S', unsigned short, unsignedShortValue)
CASE('I', unsigned int, unsignedIntValue)
CASE('L', unsigned long, unsignedLongValue)
CASE('Q', unsigned long long, unsignedLongLongValue)
CASE('f', float, floatValue)
CASE('d', double, doubleValue)
#undef CASE
default:
[self setValue: value
forUndefinedKey: key];
return;
}
}
- (void)setValue: (id)value
forUndefinedKey: (OFString *)key
{
@throw [OFUndefinedKeyException exceptionWithObject: self
key: key
value: value];
}
- (void)setNilValueForKey: (OFString *)key
{
@throw [OFInvalidArgumentException exception];
}
@end