/*
* Copyright (c) 2008-2023 Jonathan Schleifer <js@nil.im>
*
* 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 "OFArray.h"
#import "OFMethodSignature.h"
#import "OFNumber.h"
#import "OFString.h"
#import "OFInvalidArgumentException.h"
#import "OFOutOfMemoryException.h"
#import "OFUndefinedKeyException.h"
int _OFObject_KeyValueCoding_reference;
@implementation OFObject (KeyValueCoding)
- (id)valueForKey: (OFString *)key
{
void *pool = objc_autoreleasePoolPush();
SEL selector = sel_registerName(key.UTF8String);
OFMethodSignature *methodSignature =
[self methodSignatureForSelector: selector];
id ret;
if (methodSignature == nil) {
size_t keyLength;
char *name;
if ((keyLength = key.UTF8StringLength) < 1) {
objc_autoreleasePoolPop(pool);
return [self valueForUndefinedKey: key];
}
name = OFAllocMemory(keyLength + 3, 1);
@try {
memcpy(name, "is", 2);
memcpy(name + 2, key.UTF8String, keyLength);
name[keyLength + 2] = '\0';
name[2] = OFASCIIToUpper(name[2]);
selector = sel_registerName(name);
} @finally {
OFFreeMemory(name);
}
methodSignature = [self methodSignatureForSelector: selector];
if (methodSignature == NULL) {
objc_autoreleasePoolPop(pool);
return [self valueForUndefinedKey: key];
}
switch (*methodSignature.methodReturnType) {
case '@':
case '#':
objc_autoreleasePoolPop(pool);
return [self valueForUndefinedKey: key];
}
}
if (methodSignature.numberOfArguments != 2 ||
*[methodSignature argumentTypeAtIndex: 0] != '@' ||
*[methodSignature argumentTypeAtIndex: 1] != ':') {
objc_autoreleasePoolPop(pool);
return [self valueForUndefinedKey: key];
}
switch (*methodSignature.methodReturnType) {
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:
objc_autoreleasePoolPop(pool);
return [self valueForUndefinedKey: key];
}
[ret retain];
objc_autoreleasePoolPop(pool);
return [ret autorelease];
}
- (id)valueForKeyPath: (OFString *)keyPath
{
void *pool = objc_autoreleasePoolPush();
id ret = self;
for (OFString *key in [keyPath componentsSeparatedByString: @"."])
ret = [ret valueForKey: key];
[ret retain];
objc_autoreleasePoolPop(pool);
return [ret autorelease];
}
- (id)valueForUndefinedKey: (OFString *)key
{
@throw [OFUndefinedKeyException exceptionWithObject: self key: key];
}
- (void)setValue: (id)value forKey: (OFString *)key
{
void *pool = objc_autoreleasePoolPush();
size_t keyLength;
char *name;
SEL selector;
OFMethodSignature *methodSignature;
const char *valueType;
if ((keyLength = key.UTF8StringLength) < 1) {
objc_autoreleasePoolPop(pool);
[self setValue: value forUndefinedKey: key];
return;
}
name = OFAllocMemory(keyLength + 5, 1);
@try {
memcpy(name, "set", 3);
memcpy(name + 3, key.UTF8String, keyLength);
memcpy(name + keyLength + 3, ":", 2);
name[3] = OFASCIIToUpper(name[3]);
selector = sel_registerName(name);
} @finally {
OFFreeMemory(name);
}
methodSignature = [self methodSignatureForSelector: selector];
if (methodSignature == nil ||
methodSignature.numberOfArguments != 3 ||
*methodSignature.methodReturnType != 'v' ||
*[methodSignature argumentTypeAtIndex: 0] != '@' ||
*[methodSignature argumentTypeAtIndex: 1] != ':') {
objc_autoreleasePoolPop(pool);
[self setValue: value forUndefinedKey: key];
return;
}
valueType = [methodSignature argumentTypeAtIndex: 2];
if (*valueType != '@' && *valueType != '#' && value == nil) {
objc_autoreleasePoolPop(pool);
[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:
objc_autoreleasePoolPop(pool);
[self setValue: value forUndefinedKey: key];
return;
}
objc_autoreleasePoolPop(pool);
}
- (void)setValue: (id)value forKeyPath: (OFString *)keyPath
{
void *pool = objc_autoreleasePoolPush();
OFArray *keys = [keyPath componentsSeparatedByString: @"."];
size_t keysCount = keys.count;
id object = self;
size_t i = 0;
for (OFString *key in keys) {
if (++i == keysCount)
[object setValue: value forKey: key];
else
object = [object valueForKey: key];
}
objc_autoreleasePoolPop(pool);
}
- (void)setValue: (id)value forUndefinedKey: (OFString *)key
{
@throw [OFUndefinedKeyException exceptionWithObject: self
key: key
value: value];
}
- (void)setNilValueForKey: (OFString *)key
{
@throw [OFInvalidArgumentException exception];
}
@end