/* Copyright (C) 2000-2004 SKYRIX Software AG This file is part of OpenGroupware.org. OGo 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, or (at your option) any later version. OGo 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 OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // $Id$ #include "WOKeyPathAssociation.h" #include #include "NSObject+WO.h" #include "common.h" #if LIB_FOUNDATION_BOEHM_GC # if LIB_FOUNDATION_LIBRARY # include # else # error no BoehmGC support on this Foundation! # endif #endif #if NeXT_RUNTIME || APPLE_RUNTIME # include # include # include # define METHOD_NULL NULL # define object_is_instance(XXX) \ ((XXX != nil) && CLS_ISCLASS(*((Class *)XXX))) # define sel_get_uid sel_getUid # define class_get_class_method class_getClassMethod # define class_get_instance_method class_getInstanceMethod # define __CLS_INFO(cls) ((cls)->info) # ifndef __CLS_ISINFO # define __CLS_ISINFO(cls, mask) ((__CLS_INFO(cls) & mask) == mask) # endif # ifndef CLS_ISCLASS # define CLS_ISCLASS(cls) ((cls) && __CLS_ISINFO(cls, CLS_CLASS)) # endif #endif #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \ APPLE_FOUNDATION_LIBRARY bool _CFDictionaryIsMutable(CFDictionaryRef dict); #endif /* WOKeyPathAssociation This is an association class for so called keypaths. It uses extensive caching to reuse calculated method pointers. Note the -kvcIsPreferredInKeyPath method. It is used whether -valueForKey: and -takeValue:forKey: has preference or the objects methods. In usual cases it can be assumed that -valueForKey has the preference, but not in the case of WOSession, WOComponent, WOApplication, WOContext which use -valueForKey: as a fallback. Since -valueForKey: is defined as a category on NSObject it eliminates the caching scheme used. */ #if DEBUG // #define HEAVY_DEBUG 1 // #define USE_EXCEPTION_HANDLERS 1 #endif #if USE_EXCEPTION_HANDLERS # warning using local exception handlers in associations, slows down templates #endif typedef enum { WOKeyType_unknown = 0, WOKeyType_kvc = 1, WOKeyType_method = 2, WOKeyType_ivar = 3, WOKeyType_binding = 4 } WOKeyType; typedef union { void *ivar; IMP method; // real method or takeValue:ForKey: char (*cmethod) (id, SEL); unsigned char (*ucmethod)(id, SEL); int (*imethod) (id, SEL); unsigned int (*uimethod)(id, SEL); short (*smethod) (id, SEL); unsigned short (*usmethod)(id, SEL); const char * (*strmethod)(id, SEL); float (*fmethod)(id, SEL); double (*dmethod)(id, SEL); } WOGetMethodType; typedef union { IMP method; // real method or takeValue:ForKey: void (*omethod) (id, SEL, id); void (*cmethod) (id, SEL, char); void (*ucmethod) (id, SEL, unsigned char); void (*imethod) (id, SEL, int); void (*uimethod) (id, SEL, unsigned int); void (*smethod) (id, SEL, short); void (*usmethod) (id, SEL, unsigned short); void (*strmethod)(id, SEL, const char *); void (*fmethod) (id, SEL, float); void (*dmethod) (id, SEL, double); } WOSetMethodType; typedef struct { char *ckey; short keyLen:12; short isFault:1; WOKeyType type:3; id object; Class isa; WOGetMethodType access; union { NSString *key; // for valueForKey: struct { SEL get; // get method selector } sel; } extra; unsigned char retType; } WOKeyPathComponent; typedef union { id object; const char *cstr; int sint; unsigned int uint; short ss; unsigned short us; unsigned char c; float flt; double dbl; } WOReturnValueHolder; #define intNumObj(__VAL__) \ (__VAL__==0?inum0:(__VAL__==1?inum1:[NumberClass numberWithInt:__VAL__])) #define uintNumObj(__VAL__) \ (__VAL__ ==0 ? uinum0 : \ (__VAL__==1?uinum1:[NumberClass numberWithUnsignedInt:__VAL__])) @implementation WOKeyPathAssociation + (int)version { return 2; } static Class NumberClass = Nil; static Class StringClass = Nil; #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \ APPLE_FOUNDATION_LIBRARY static Class NSCFDictionaryClass = Nil; #endif static int debugOn = -1; #define IS_NUMSTR(__VAL__) (__VAL__>=0 && __VAL__<50) #define IS_UNUMSTR(__VAL__) (__VAL__<50) static NSString *numStrings[] = { @"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"10", @"11", @"12", @"13", @"14", @"15", @"16", @"17", @"18", @"19", @"20", @"21", @"22", @"23", @"24", @"25", @"26", @"27", @"28", @"29", @"30", @"31", @"32", @"33", @"34", @"35", @"36", @"37", @"38", @"39", @"40", @"41", @"42", @"43", @"44", @"45", @"46", @"47", @"48", @"49" }; static NSNumber *inum0 = nil, *inum1 = nil; static NSNumber *uinum0 = nil, *uinum1 = nil; + (void)initialize { static BOOL isInitialized = NO; if (isInitialized) return; isInitialized = YES; NSAssert2([super version] == 2, @"invalid superclass (%@) version %i !", NSStringFromClass([self superclass]), [super version]); debugOn = [[[NSUserDefaults standardUserDefaults] objectForKey:@"WODebugKeyPathAssociation"] boolValue] ? 1 : 0; NumberClass = [NSNumber class]; StringClass = [NSString class]; #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \ APPLE_FOUNDATION_LIBRARY NSCFDictionaryClass = NSClassFromString(@"NSCFDictionary"); #endif inum0 = [[NumberClass numberWithInt:0] retain]; inum1 = [[NumberClass numberWithInt:1] retain]; uinum0 = [[NumberClass numberWithUnsignedInt:0] retain]; uinum1 = [[NumberClass numberWithUnsignedInt:1] retain]; } static inline WOKeyPathComponent * _getComponent(register WOKeyPathAssociation *self, register unsigned _idx) { return (WOKeyPathComponent *) (self->keyPath + (_idx * sizeof(WOKeyPathComponent))); } static inline void _freeKeyPathComponent(WOKeyPathComponent *info) { if (info->ckey) { free(info->ckey); info->ckey = NULL; } if ((info->type == WOKeyType_kvc) || (info->type == WOKeyType_binding)) { [info->extra.key release]; info->extra.key = nil; } info->object = nil; info->isa = Nil; } static inline void _parseKeyPath(WOKeyPathAssociation *self,NSString *_keyPath) { unsigned keyLen = [_keyPath cStringLength]; char *buf; char *cstr; char *tmp; buf = malloc(keyLen + 1); cstr = buf; tmp = (char *)cstr; [_keyPath getCString:buf]; buf[keyLen] = '\0'; // get number of components self->size = 1; while (*tmp) { if (*tmp == '.') (self->size)++; tmp++; } self->keyPath = calloc(self->size, sizeof(WOKeyPathComponent)); // transfer components { unsigned char pos; tmp = (char *)cstr; for (pos = 0; pos < self->size; pos++) { WOKeyPathComponent *info = _getComponent(self, pos); // goto end or next '.' while ((*tmp != '\0') && (*tmp != '.')) tmp++; info->keyLen = (tmp - cstr); info->ckey = malloc(info->keyLen + 4); memcpy(info->ckey, cstr, info->keyLen); info->ckey[info->keyLen] = '\0'; NSCAssert(strlen(info->ckey) > 0, @"invalid ckey .."); NSCAssert((int)strlen(info->ckey) == (int)info->keyLen, @"size and content differ"); info->object = nil; info->isa = Nil; info->type = WOKeyType_unknown; cstr = tmp + 1; tmp = (char *)cstr; } } free(buf); } - (id)initWithKeyPath:(NSString *)_keyPath { if ([_keyPath length] < 1) { self = [self autorelease]; self = nil; NSLog(@"ERROR: passed invalid keypath (%@) to association !", _keyPath); return nil; } if ((self = [super init])) { _parseKeyPath(self, _keyPath); } return self; } - (id)init { NSLog(@"keypaths can not be created using 'init' .."); [NSException raise:@"InvalidUseOfMethodException" format:@"keypaths can not be created using 'init'"]; return nil; } - (void)dealloc { if (self->keyPath) { int cnt; for (cnt = 0; cnt < self->size; cnt++) _freeKeyPathComponent(_getComponent(self, cnt)); free(self->keyPath); } [super dealloc]; } /* accessors */ - (NSString *)keyPath { register unsigned char pos; char *buffer; unsigned len; // get size len = self->size; // size-1 '.' chars and the '\0' char for (pos = 0; pos < self->size; pos++) { WOKeyPathComponent *info = _getComponent(self, pos); len += info->keyLen; } buffer = malloc(len + 4); // transfer contents for (pos = 0, len = 0; pos < self->size; pos++) { WOKeyPathComponent *info = _getComponent(self, pos); if (pos != 0) { buffer[len] = '.'; len++; } memcpy(&(buffer[len]), info->ckey, info->keyLen); len += info->keyLen; } buffer[len] = '\0'; return [[[StringClass alloc] initWithCStringNoCopy:buffer length:len freeWhenDone:YES] autorelease]; } - (id)initWithString:(NSString *)_s { return [self initWithKeyPath:_s]; } /* value */ static inline void _fillInfo(WOKeyPathAssociation *self, id object, WOKeyPathComponent *info) { Class clazz = [object class]; BOOL needRefill = NO; if (info->isFault) needRefill = YES; else if (info->type == WOKeyType_unknown) // first invocation of 'value' needRefill = YES; else if ((info->object == nil) || (object == nil)) needRefill = YES; else if (info->isa != clazz) needRefill = YES; else if (info->object != object) needRefill = YES; if (!needRefill) // object is the same, can use cached representation return; { #if NeXT_RUNTIME struct objc_method *method = NULL; #else Method_t method = METHOD_NULL; #endif if ((info->type == WOKeyType_kvc) || (info->type == WOKeyType_binding)) { /* release old key */ [info->extra.key release]; info->extra.key = nil; } info->type = WOKeyType_unknown; info->retType = _C_ID; if (*(info->ckey) == '^') { /* a binding key */ method = class_get_instance_method(clazz, @selector(valueForBinding:)); info->type = WOKeyType_binding; info->extra.key = [[StringClass alloc] initWithCString:(info->ckey + 1)]; } else { if (object) { if (object_is_instance(object)) { if ([object kvcIsPreferredInKeyPath]) { method = class_get_instance_method(clazz, @selector(valueForKey:)); if (method != METHOD_NULL) { info->type = WOKeyType_kvc; info->extra.key = [[StringClass alloc] initWithCString:info->ckey]; } else { info->extra.sel.get = sel_get_uid(info->ckey); method = class_get_instance_method(clazz, info->extra.sel.get); if (method != METHOD_NULL) info->type = WOKeyType_method; } } else { info->extra.sel.get = sel_get_uid(info->ckey); method = class_get_instance_method(clazz, info->extra.sel.get); if (method != METHOD_NULL) info->type = WOKeyType_method; else { method = class_get_instance_method(clazz, @selector(valueForKey:)); if (method != METHOD_NULL) { info->type = WOKeyType_kvc; info->extra.key = [[StringClass alloc] initWithCString:info->ckey]; } } } } else { /* object is a class */ method = class_get_class_method(object, @selector(valueForKey:)); if (method != METHOD_NULL) { info->type = WOKeyType_kvc; info->extra.key = [[StringClass alloc] initWithCString:info->ckey]; } else { info->extra.sel.get = sel_get_uid(info->ckey); method = class_get_class_method(*(Class *)object, info->extra.sel.get); if (method != METHOD_NULL) { info->type = WOKeyType_method; } } } } } info->object = object; info->isa = [object class]; info->isFault = [object isFault]; if (method != METHOD_NULL) { #if NeXT_RUNTIME info->access.method = method->method_imp; #else info->access.method = method_get_imp(method); #endif info->retType = *(method->method_types); } #if HEAVY_DEBUG NSLog(@"type is %i, key is '%s'", info->type, info->ckey); #endif } } - (NSException *)handleGetException:(NSException *)_exception { static NSString *excKey = @"exception"; static NSString *kpKey = @"keyPath"; static NSString *assocKey = @"association"; static NSString *excName = @"WOKeyPathException"; static NSString *excReason = @"couldn't get value for a keypath component"; NSException *e; NSDictionary *ui; ui = [NSDictionary dictionaryWithObjectsAndKeys: _exception, excKey, [self keyPath], kpKey, self, assocKey, nil]; e = [[NSException alloc] initWithName:excName reason:excReason userInfo:ui]; return e; } static WOReturnValueHolder _getComponentValue(WOKeyPathAssociation *self, id object, WOKeyPathComponent *info) { WOReturnValueHolder retValue; #if DEBUG NSCAssert1(info, @"%s: missing info !", __PRETTY_FUNCTION__); #endif _fillInfo(self, object, info); // execute if (info->type == WOKeyType_method) { #if HEAVY_DEBUG NSLog(@"get key %s of keyPath %@\n" @" from: 0x%08X[%@]\n" @" via method (ret %c)", info->ckey, [self keyPath], object, NSStringFromClass([object class]), info->retType); #endif #if USE_EXCEPTION_HANDLERS NS_DURING { #endif if ((info->retType == _C_ID) || (info->retType == _C_CLASS)) { retValue.object = info->access.method(object, info->extra.sel.get); #if HEAVY_DEBUG NSLog(@" got result 0x%08X[%@]: %@", retValue.object, NSStringFromClass([retValue.object class]), retValue.object); #endif } else { switch (info->retType) { case _C_ID: case _C_CLASS: retValue.object = info->access.method(object, info->extra.sel.get); break; case _C_VOID: retValue.object = object; break; case _C_CHR: case _C_UCHR: retValue.c = info->access.ucmethod(object, info->extra.sel.get); break; case _C_INT: retValue.sint = info->access.imethod(object, info->extra.sel.get); break; case _C_UINT: retValue.uint = info->access.uimethod(object, info->extra.sel.get); break; case _C_SHT: retValue.ss = info->access.smethod(object, info->extra.sel.get); break; case _C_USHT: retValue.us = info->access.usmethod(object, info->extra.sel.get); break; case _C_FLT: retValue.flt = info->access.fmethod(object, info->extra.sel.get); break; case _C_DBL: retValue.dbl = info->access.dmethod(object, info->extra.sel.get); break; case _C_CHARPTR: retValue.cstr = info->access.strmethod(object, info->extra.sel.get); break; default: NSLog(@"%@: unsupported type '%c' !", self, info->retType); [NSException raise:@"WORuntimeException" format: @"in WOKeyPathAssociation %@: unsupported type '%c'", self, info->retType]; break; } } #if USE_EXCEPTION_HANDLERS } NS_HANDLER [[self handleGetException:localException] raise]; NS_ENDHANDLER; #endif } else if (info->type == WOKeyType_kvc) { #if HEAVY_DEBUG NSLog(@"get keyPath %@ from %@ via KVC", [self keyPath], object); #endif #if 0 NSLog(@"ckey: %s", info->ckey); NSLog(@"key-class: %s", (*(Class *)info->extra.key)->name); NSLog(@"key: %@", info->extra.key); #endif #if LIB_FOUNDATION_BOEHM_GC [GarbageCollector collectGarbages]; #endif retValue.object = info->access.method(object, @selector(valueForKey:), info->extra.key); } else if (info->type == WOKeyType_binding) { #if HEAVY_DEBUG NSLog(@"get keyPath %@ from %@ via binding", [self keyPath], object); #endif retValue.object = info->access.method(object, @selector(valueForBinding:), info->extra.key); } else { // unknown || ivar #if HEAVY_DEBUG NSLog(@"unknown info type for keyPath %@ from %@ !!", [self keyPath], object); #endif retValue.object = nil; } return retValue; } static inline id _objectify(unsigned char _type, WOReturnValueHolder *_value) { id result = nil; //NSLog(@"shall convert value of type '%c'", _type); switch (_type) { case _C_ID: case _C_CLASS: result = _value->object; break; case _C_VOID: result = _value->object; break; case _C_CHR: case _C_UCHR: result = [NumberClass numberWithUnsignedChar:_value->c]; break; case _C_INT: result = intNumObj(_value->sint); break; case _C_UINT: result = uintNumObj(_value->uint); break; case _C_SHT: result = [NumberClass numberWithShort:_value->ss]; break; case _C_USHT: result = [NumberClass numberWithUnsignedShort:_value->us]; break; case _C_FLT: result = [NumberClass numberWithFloat:_value->flt]; break; case _C_DBL: result = [NumberClass numberWithDouble:_value->dbl]; break; case _C_CHARPTR: result = _value->cstr ? [StringClass stringWithCString:_value->cstr] : nil; break; default: NSLog(@"unsupported type '%c' !", _type); [NSException raise:@"WORuntimeException" format:@"in WOKeyPathAssociation: unsupported type '%c'", _type]; break; } //NSLog(@"made %@[0x%08X].", NSStringFromClass([result class]), result); return result; } static inline id _getValueN(WOKeyPathAssociation *self, unsigned _count, id root) { register unsigned cnt; id object = root; for (cnt = 0; (cnt < _count) && (object != nil); cnt++) { WOKeyPathComponent *info; WOReturnValueHolder retValue; info = _getComponent(self, cnt); #if DEBUG NSCAssert1(info, @"%s: missing info !", __PRETTY_FUNCTION__); #endif retValue = _getComponentValue(self, object, info); object = (info->type == WOKeyType_method) ? _objectify(info->retType, &retValue) : retValue.object; } //NSLog(@"object %@ for keyPath %@", object, [self keyPath]); return object; } static inline id _getValue(WOKeyPathAssociation *self, id root) { return _getValueN(self, self->size, root); } static id _getOneValue(WOKeyPathAssociation *self, id root) { WOKeyPathComponent *info; WOReturnValueHolder retValue; info = (WOKeyPathComponent *)self->keyPath; retValue = _getComponentValue(self, root, info); return (info->type == WOKeyType_method) ? _objectify(info->retType, &retValue) : retValue.object; } static inline void _getSetSelName(register unsigned char *buf, register const unsigned char *_key, register unsigned _len) { buf[0] = 's'; buf[1] = 'e'; buf[2] = 't'; switch (_len) { case 0: break; case 1: buf[3] = _key[0]; break; case 2: buf[3] = _key[0]; buf[4] = _key[1]; break; case 3: buf[3] = _key[0]; buf[4] = _key[1]; buf[5] = _key[2]; break; case 4: buf[3] = _key[0]; buf[4] = _key[1]; buf[5] = _key[2]; buf[6] = _key[3]; break; case 5: buf[3] = _key[0]; buf[4] = _key[1]; buf[5] = _key[2]; buf[6] = _key[3]; buf[7] = _key[4]; break; case 6: buf[3] = _key[0]; buf[4] = _key[1]; buf[5] = _key[2]; buf[6] = _key[3]; buf[7] = _key[4]; buf[8] = _key[5]; break; default: memcpy(&(buf[3]), _key, _len); break; } buf[3] = toupper(buf[3]); buf[_len + 3] = ':'; buf[_len + 4] = '\0'; } static inline SEL _getSetSel(register const unsigned char *_key, register unsigned _len) { char buf[259]; _getSetSelName(buf, _key, _len); return sel_get_uid(buf); } static BOOL _setValue(WOKeyPathAssociation *self, id _value, id root) { WOKeyPathComponent *info; id object = root; if (self->size > 1) object = _getValueN(self, self->size - 1, root); if (object == nil) // nothing to set .. return YES; // receiver == nil isn't an error condition info = _getComponent(self, self->size - 1); NSCAssert(info->keyLen < 255, @"keysize to big .."); _fillInfo(self, object, info); if (info->type == WOKeyType_method) { // determine set-selector SEL setSel = _getSetSel(info->ckey, info->keyLen); if (![object respondsToSelector:setSel]) { #if 0 NSLog(@"%@: Could not set value for key '%s', " @"object %@ doesn't respond to %@.", self, info->ckey, object, setSel ? NSStringFromSelector(setSel) : @""); #endif return NO; } else { WOSetMethodType sm; if ((sm.method = [object methodForSelector:setSel])) { switch (info->retType) { case _C_CLASS: case _C_ID: sm.omethod(object, setSel, _value); break; case _C_CHR: sm.cmethod(object, setSel, [_value charValue]); break; case _C_UCHR: sm.ucmethod(object, setSel, [_value unsignedCharValue]); break; case _C_SHT: sm.smethod(object, setSel, [_value shortValue]); break; case _C_USHT: sm.usmethod(object, setSel, [_value unsignedShortValue]); break; case _C_INT: sm.imethod(object, setSel, [_value intValue]); break; case _C_UINT: sm.uimethod(object, setSel, [_value unsignedIntValue]); break; case _C_FLT: sm.fmethod(object, setSel, [_value floatValue]); break; case _C_DBL: sm.dmethod(object, setSel, [_value doubleValue]); break; case _C_CHARPTR: if (_value == nil) sm.strmethod(object, setSel, NULL); else { unsigned clen; if ((clen = [_value cStringLength]) == 0) sm.strmethod(object, setSel, ""); else { unsigned char *buf; buf = malloc(clen + 4); [_value getCString:buf]; buf[clen] = '\0'; sm.strmethod(object, setSel, buf); if (buf) free(buf); } } break; default: NSLog(@"%@: cannot set type '%c' yet (key=%s, method=%@) ..", self, info->retType, info->ckey, NSStringFromSelector(setSel)); [NSException raise:@"WORuntimeException" format: @"in WOKeyPathAssociation %@: " @"cannot set type '%c' yet (key=%s, method=%@)", self, info->retType, info->ckey, NSStringFromSelector(setSel)]; return NO; } return YES; } else { NSLog(@"%@: did not find method %@ in object %@", self, NSStringFromSelector(setSel), object); return NO; } } } else if (info->type == WOKeyType_kvc) { // takeValue:forKey:.. NSCAssert(info->extra.key, @"no key object set .."); #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \ APPLE_FOUNDATION_LIBRARY if([object isKindOfClass:NSCFDictionaryClass] && !_CFDictionaryIsMutable((CFDictionaryRef)object)) return NO; #endif [object takeValue:_value forKey:info->extra.key]; return YES; } else if (info->type == WOKeyType_binding) { // setValue:forBinding: NSCAssert(info->extra.key, @"no key object set .."); [object setValue:_value forBinding:info->extra.key]; return YES; } else { NSLog(@"%@: Could not set value for key '%s'.", self, info->ckey); return NO; } } - (void)setValue:(id)_value inComponent:(WOComponent *)_component { if (debugOn) NSLog(@"%@: set value %@ component %@", self, _value, _component); _setValue(self, _value, _component); } - (id)valueInComponent:(WOComponent *)_component { #if DEBUG volatile id result; if (debugOn) NSLog(@"%@: get value in component %@", self, _component); #if USE_EXCEPTION_HANDLERS NS_DURING { #endif result = (self->size > 1) ? _getValue(self, _component) : _getOneValue(self, _component); #if USE_EXCEPTION_HANDLERS } NS_HANDLER { fprintf(stderr, "during evaluation of keypath %s:\n %s\n", [[self description] cString], [[localException description] cString]); fflush(stderr); [localException raise]; } NS_ENDHANDLER; #endif return result; #else if (debugOn) NSLog(@"%@: get value in component %@", self, _component); return (self->size > 1) ? _getValue(self, _component) : _getOneValue(self, _component); #endif } - (BOOL)isValueConstant { return NO; } - (BOOL)isValueSettable { return YES; } /* special values */ - (void)setUnsignedIntValue:(unsigned int)_value inComponent:(WOComponent *)_wo { WOKeyPathComponent *info; if (debugOn) NSLog(@"%@: set uint value %i in component %@", self, _value, _wo); if (self->size > 1) { _setValue(self, uintNumObj(_value), _wo); return; } info = (WOKeyPathComponent *)self->keyPath; NSCAssert(info->keyLen < 255, @"keysize to big .."); _fillInfo(self, _wo, info); if (info->type == WOKeyType_method) { /* determine set-selector */ if (info->retType == _C_CHR || info->retType == _C_UCHR || info->retType == _C_INT || info->retType == _C_UINT) { SEL setSel; WOSetMethodType sm; setSel = _getSetSel(info->ckey, info->keyLen); sm.method = [_wo methodForSelector:setSel]; NSAssert1(sm.method, @"didn't find method for key %s", info->ckey); switch (info->retType) { case _C_CHR: { if (((int)_value < -126) || ((int)_value > 127)) NSLog(@"%@: value (%i) out of range for char !", self, _value); sm.cmethod(_wo, setSel, (char)_value); break; } case _C_UCHR: { if ((_value < 0) || (_value > 255)) NSLog(@"%@: value (%i) out of range for uchar !", self, _value); sm.ucmethod(_wo, setSel, (unsigned char)_value); break; } case _C_INT: { sm.imethod(_wo, setSel, (int)_value); break; } case _C_UINT: { sm.uimethod(_wo, setSel, (unsigned int)_value); break; } default: [NSException raise:@"WORuntimeException" format: @"in WOKeyPathAssociation %@: " @"does not handle type %c", self, info->retType]; break; } } else { // usual setValue _setValue(self, uintNumObj(_value), _wo); } return; } if (info->type == WOKeyType_kvc) { // takeValue:forKey:.. NSCAssert(info->extra.key, @"no key object set .."); [_wo takeValue:uintNumObj(_value) forKey:info->extra.key]; return; } if (info->type == WOKeyType_binding) { // setValue:forBinding: NSCAssert(info->extra.key, @"no key object set .."); [_wo setValue:uintNumObj(_value) forBinding:info->extra.key]; return; } NSLog(@"%@: Could not set value for key '%s'.", self, info->ckey); } - (unsigned int)unsignedIntValueInComponent:(WOComponent *)_component { WOKeyPathComponent *info; WOReturnValueHolder retValue; if (debugOn) NSLog(@"%@: get uint value in component %@", self, _component); if (self->size > 1) return [_getValue(self, _component) unsignedIntValue]; info = (WOKeyPathComponent *)self->keyPath; retValue = _getComponentValue(self, _component, info); if (info->type == WOKeyType_method) { switch (info->retType) { case _C_UINT: return retValue.uint; case _C_INT: return retValue.sint; case _C_UCHR: return retValue.c; case _C_CHR: return retValue.c; case _C_SHT: return retValue.ss; case _C_USHT: return retValue.us; } return [_objectify(info->retType, &retValue) unsignedIntValue]; } #if 0 NSLog(@"ret value object for key '%s' is 0x%08X", info->ckey, retValue.object); NSLog(@"ret value object class is %@", [retValue.object class]); #endif return [retValue.object unsignedIntValue]; } - (void)setIntValue:(int)_value inComponent:(WOComponent *)_wo { WOKeyPathComponent *info; if (debugOn) NSLog(@"%@: set int value %i in component %@", self, _value, _wo); if (self->size > 1) { _setValue(self, intNumObj(_value), _wo); return; } info = (WOKeyPathComponent *)self->keyPath; NSCAssert(info->keyLen < 255, @"keysize to big .."); _fillInfo(self, _wo, info); if (info->type == WOKeyType_method) { // determine set-selector if (info->retType == _C_CHR || info->retType == _C_UCHR || info->retType == _C_INT || info->retType == _C_UINT) { SEL setSel; WOSetMethodType sm; setSel = _getSetSel(info->ckey, info->keyLen); sm.method = [_wo methodForSelector:setSel]; NSAssert1(sm.method, @"didn't find method for key %s", info->ckey); switch (info->retType) { case _C_CHR: { if (((int)_value < -126) || ((int)_value > 127)) NSLog(@"%@: value (%i) out of range for char !", self, _value); sm.cmethod(_wo, setSel, (char)_value); break; } case _C_UCHR: { if ((_value < 0) || (_value > 255)) NSLog(@"%@: value (%i) out of range for uchar !", self, _value); sm.ucmethod(_wo, setSel, (unsigned char)_value); break; } case _C_INT: { sm.imethod(_wo, setSel, (int)_value); break; } case _C_UINT: { sm.uimethod(_wo, setSel, (unsigned int)_value); break; } default: [NSException raise:@"WORuntimeException" format: @"in WOKeyPathAssociation %@: " @"does not handle type %c", self, info->retType]; break; } } else { // usual setValue _setValue(self, intNumObj(_value), _wo); } return; } if (info->type == WOKeyType_kvc) { // takeValue:forKey: NSCAssert(info->extra.key, @"no key object set .."); [_wo takeValue:intNumObj(_value) forKey:info->extra.key]; return; } if (info->type == WOKeyType_binding) { // setValue:forBinding: NSCAssert(info->extra.key, @"no key object set .."); [_wo setValue:intNumObj(_value) forBinding:info->extra.key]; return; } NSLog(@"%@: Could not set value for key '%s'.", self, info->ckey); } - (int)intValueInComponent:(WOComponent *)_component { WOKeyPathComponent *info; WOReturnValueHolder retValue; if (debugOn) NSLog(@"%@: get int value in component %@", self, _component); if (self->size > 1) return [_getValue(self, _component) intValue]; info = (WOKeyPathComponent *)self->keyPath; retValue = _getComponentValue(self, _component, info); if (info->type != WOKeyType_method) return [retValue.object intValue]; switch (info->retType) { case _C_UINT: return retValue.uint; case _C_INT: return retValue.sint; case _C_UCHR: return retValue.c; case _C_CHR: return retValue.c; case _C_SHT: return retValue.ss; case _C_USHT: return retValue.us; default: return [_objectify(info->retType, &retValue) intValue]; } } - (void)setBoolValue:(BOOL)_value inComponent:(WOComponent *)_wo { WOKeyPathComponent *info; if (debugOn) NSLog(@"%@: set bool value %i in component %@", self, _value, _wo); if (self->size > 1) { _setValue(self, [NumberClass numberWithBool:_value], _wo); return; } info = (WOKeyPathComponent *)self->keyPath; NSCAssert(info->keyLen < 255, @"keysize to big .."); _fillInfo(self, _wo, info); if (info->type == WOKeyType_method) { // determine set-selector if (info->retType == _C_CHR || info->retType == _C_UCHR || info->retType == _C_INT || info->retType == _C_UINT) { SEL setSel; WOSetMethodType sm; setSel = _getSetSel(info->ckey, info->keyLen); sm.method = [_wo methodForSelector:setSel]; NSAssert1(sm.method, @"didn't find method for key %s", info->ckey); switch (info->retType) { case _C_CHR: { sm.cmethod(_wo, setSel, (char)_value); break; } case _C_UCHR: { sm.ucmethod(_wo, setSel, (unsigned char)_value); break; } case _C_INT: { sm.imethod(_wo, setSel, (int)_value); break; } case _C_UINT: { sm.uimethod(_wo, setSel, (unsigned int)_value); break; } default: [NSException raise:@"WORuntimeException" format: @"in WOKeyPathAssociation %@: " @"does not handle type %c", self, info->retType]; break; } } else { // usual setValue _setValue(self, [NumberClass numberWithBool:_value], _wo); } } else if (info->type == WOKeyType_kvc) { // takeValue:forKey: NSCAssert(info->extra.key, @"no key object set .."); [_wo takeValue:[NumberClass numberWithBool:_value] forKey:info->extra.key]; } else if (info->type == WOKeyType_binding) { // setValue:forBinding: NSCAssert(info->extra.key, @"no key object set .."); [_wo setValue:[NumberClass numberWithBool:_value] forBinding:info->extra.key]; } else { NSLog(@"%@: Could not set value for key '%s'.", self, info->ckey); } } - (BOOL)boolValueInComponent:(WOComponent *)_component { if (debugOn) NSLog(@"%@: get bool value in component %@", self, _component); if (self->size > 1) return [_getValue(self, _component) boolValue]; else { WOKeyPathComponent *info; WOReturnValueHolder retValue; info = (WOKeyPathComponent *)self->keyPath; retValue = _getComponentValue(self, _component, info); if (info->type == WOKeyType_method) { switch (info->retType) { case _C_UINT: return retValue.uint; case _C_INT: return retValue.sint; case _C_UCHR: return retValue.c; case _C_CHR: return retValue.c; case _C_SHT: return retValue.ss; case _C_USHT: return retValue.us; default: return [_objectify(info->retType, &retValue) boolValue]; } } else return [retValue.object boolValue]; } } - (void)setStringValue:(NSString *)_value inComponent:(WOComponent *)_wo { if (debugOn) NSLog(@"%@: set string value '%@' in component %@", self, _value, _wo); _setValue(self, _value, _wo); } - (NSString *)stringValueInComponent:(WOComponent *)_component { WOKeyPathComponent *info; WOReturnValueHolder retValue; if (debugOn) NSLog(@"%@: get string value in component %@", self, _component); if (self->size > 1) return [_getValue(self, _component) stringValue]; info = (WOKeyPathComponent *)self->keyPath; retValue = _getComponentValue(self, _component, info); if (info->type != WOKeyType_method) return [retValue.object stringValue]; switch (info->retType) { case _C_UINT: if (IS_UNUMSTR(retValue.uint)) return numStrings[retValue.uint]; return [StringClass stringWithFormat:@"%u", retValue.uint]; case _C_INT: if (IS_NUMSTR(retValue.sint)) return numStrings[retValue.sint]; return [StringClass stringWithFormat:@"%d", retValue.sint]; case _C_UCHR: if (IS_UNUMSTR(retValue.c)) return numStrings[retValue.c]; return [StringClass stringWithFormat:@"%d", (int)retValue.c]; case _C_CHR: if (IS_NUMSTR((char)retValue.c)) return numStrings[retValue.c]; return [StringClass stringWithFormat:@"%d", (int)retValue.c]; case _C_SHT: if (IS_NUMSTR(retValue.ss)) return numStrings[retValue.ss]; return [StringClass stringWithFormat:@"%d", (int)retValue.ss]; case _C_USHT: if (IS_UNUMSTR(retValue.us)) return numStrings[retValue.us]; return [StringClass stringWithFormat:@"%d", (int)retValue.us]; #if 0 case _C_FLT: return [StringClass stringWithFormat:@"%0.7g", retValue.flt]; #endif default: return [_objectify(info->retType, &retValue) stringValue]; } } /* NSCoding */ - (void)encodeWithCoder:(NSCoder *)_coder { [_coder encodeObject:[self keyPath]]; } - (id)initWithCoder:(NSCoder *)_coder { return [self initWithKeyPath:[_coder decodeObject]]; } /* NSCopying */ - (id)copyWithZone:(NSZone *)_zone { /* keypath associations are immutable */ return [self retain]; } /* description */ - (NSString *)description { return [StringClass stringWithFormat:@"<%@[0x%08X]: keyPath=%@>", NSStringFromClass([self class]), self, [self keyPath]]; } @end /* WOKeyPathAssociation */