]> err.no Git - sope/blob - sope-appserver/NGObjWeb/Associations/WOKeyPathAssociation.m
fixed OGo bug #888
[sope] / sope-appserver / NGObjWeb / Associations / WOKeyPathAssociation.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21 // $Id$
22
23 #include "WOKeyPathAssociation.h"
24 #include <NGObjWeb/WOComponent.h>
25 #include "NSObject+WO.h"
26 #include "common.h"
27
28 #if LIB_FOUNDATION_BOEHM_GC
29 #  if LIB_FOUNDATION_LIBRARY
30 #    include <extensions/GarbageCollector.h>
31 #  else
32 #    error no BoehmGC support on this Foundation!
33 #  endif
34 #endif
35
36 #if NeXT_RUNTIME || APPLE_RUNTIME
37 #  include <objc/objc.h>
38 #  include <objc/objc-api.h>
39 #  include <objc/objc-class.h>
40
41 #  define METHOD_NULL NULL
42 #  define object_is_instance(XXX) \
43      ((XXX != nil) && CLS_ISCLASS(*((Class *)XXX)))
44 #  define sel_get_uid               sel_getUid
45 #  define class_get_class_method    class_getClassMethod
46 #  define class_get_instance_method class_getInstanceMethod
47
48 #  define __CLS_INFO(cls)         ((cls)->info)
49 #  ifndef __CLS_ISINFO
50 #    define __CLS_ISINFO(cls, mask) ((__CLS_INFO(cls) & mask) == mask)
51 #  endif
52 #  ifndef CLS_ISCLASS
53 #    define CLS_ISCLASS(cls) ((cls) && __CLS_ISINFO(cls, CLS_CLASS))
54 #  endif
55 #endif
56
57 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \
58     APPLE_FOUNDATION_LIBRARY
59 bool _CFDictionaryIsMutable(CFDictionaryRef dict);
60 #endif
61
62
63 /*
64   WOKeyPathAssociation
65
66   This is an association class for so called keypaths. It uses extensive 
67   caching to reuse calculated method pointers.
68
69   Note the -kvcIsPreferredInKeyPath method. It is used whether -valueForKey: 
70   and -takeValue:forKey: has preference or the objects methods. In usual cases
71   it can be assumed that -valueForKey has the preference, but not in the case 
72   of
73
74     WOSession, WOComponent, WOApplication, WOContext
75
76   which use -valueForKey: as a fallback. Since -valueForKey: is defined as a
77   category on NSObject it eliminates the caching scheme used.
78 */
79
80 #if DEBUG
81 // #define HEAVY_DEBUG 1
82 // #define USE_EXCEPTION_HANDLERS 1
83 #endif
84
85 #if USE_EXCEPTION_HANDLERS
86 #  warning using local exception handlers in associations, slows down templates
87 #endif
88
89 typedef enum {
90   WOKeyType_unknown = 0,
91   WOKeyType_kvc     = 1,
92   WOKeyType_method  = 2,
93   WOKeyType_ivar    = 3,
94   WOKeyType_binding = 4
95 } WOKeyType;
96
97 typedef union {
98   void           *ivar;
99   IMP            method; // real method or takeValue:ForKey:
100   char           (*cmethod) (id, SEL);
101   unsigned char  (*ucmethod)(id, SEL);
102   int            (*imethod) (id, SEL);
103   unsigned int   (*uimethod)(id, SEL);
104   short          (*smethod) (id, SEL);
105   unsigned short (*usmethod)(id, SEL);
106   const char *   (*strmethod)(id, SEL);
107   float          (*fmethod)(id, SEL);
108   double         (*dmethod)(id, SEL);
109 } WOGetMethodType;
110
111 typedef union {
112   IMP  method; // real method or takeValue:ForKey:
113   void (*omethod)  (id, SEL, id);
114   void (*cmethod)  (id, SEL, char);
115   void (*ucmethod) (id, SEL, unsigned char);
116   void (*imethod)  (id, SEL, int);
117   void (*uimethod) (id, SEL, unsigned int);
118   void (*smethod)  (id, SEL, short);
119   void (*usmethod) (id, SEL, unsigned short);
120   void (*strmethod)(id, SEL, const char *);
121   void (*fmethod)  (id, SEL, float);
122   void (*dmethod)  (id, SEL, double);
123 } WOSetMethodType;
124
125 typedef struct {
126   char            *ckey;
127   short           keyLen:12;
128   short           isFault:1;
129   WOKeyType       type:3;
130   id              object;
131   Class           isa;
132   WOGetMethodType access;
133   union {
134     NSString *key; // for valueForKey:
135     struct {
136       SEL get; // get method selector
137     } sel;
138   } extra;
139   unsigned char retType;
140 } WOKeyPathComponent;
141
142 typedef union {
143   id             object;
144   const char     *cstr;
145   int            sint;
146   unsigned int   uint;
147   short          ss;
148   unsigned short us;
149   unsigned char  c;
150   float          flt;
151   double         dbl;
152 } WOReturnValueHolder;
153
154 #define intNumObj(__VAL__) \
155   (__VAL__==0?inum0:(__VAL__==1?inum1:[NumberClass numberWithInt:__VAL__]))
156
157 #define uintNumObj(__VAL__) \
158   (__VAL__ ==0 ? uinum0 : \
159   (__VAL__==1?uinum1:[NumberClass numberWithUnsignedInt:__VAL__]))
160
161 @implementation WOKeyPathAssociation
162
163 + (int)version {
164   return 2;
165 }
166
167 static Class NumberClass = Nil;
168 static Class StringClass = Nil;
169 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \
170     APPLE_FOUNDATION_LIBRARY
171 static Class NSCFDictionaryClass = Nil;
172 #endif
173 static int debugOn = -1;
174
175 #define IS_NUMSTR(__VAL__)  (__VAL__>=0 && __VAL__<50)
176 #define IS_UNUMSTR(__VAL__) (__VAL__<50)
177 static NSString *numStrings[] = {
178   @"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9",
179   @"10", @"11", @"12", @"13", @"14", @"15", @"16", @"17", @"18", @"19",
180   @"20", @"21", @"22", @"23", @"24", @"25", @"26", @"27", @"28", @"29",
181   @"30", @"31", @"32", @"33", @"34", @"35", @"36", @"37", @"38", @"39",
182   @"40", @"41", @"42", @"43", @"44", @"45", @"46", @"47", @"48", @"49"
183 };
184 static NSNumber *inum0  = nil, *inum1  = nil;
185 static NSNumber *uinum0 = nil, *uinum1 = nil;
186
187 + (void)initialize {
188   static BOOL isInitialized = NO;
189
190   if (isInitialized) return;
191   isInitialized = YES;
192   NSAssert2([super version] == 2,
193             @"invalid superclass (%@) version %i !",
194             NSStringFromClass([self superclass]), [super version]);
195
196   
197   debugOn = [[[NSUserDefaults standardUserDefaults]
198                               objectForKey:@"WODebugKeyPathAssociation"]
199                               boolValue] ? 1 : 0;
200   
201   NumberClass = [NSNumber class];
202   StringClass = [NSString class];
203 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \
204     APPLE_FOUNDATION_LIBRARY
205   NSCFDictionaryClass = NSClassFromString(@"NSCFDictionary");
206 #endif
207
208   inum0  = [[NumberClass numberWithInt:0] retain];
209   inum1  = [[NumberClass numberWithInt:1] retain];
210   uinum0 = [[NumberClass numberWithUnsignedInt:0] retain];
211   uinum1 = [[NumberClass numberWithUnsignedInt:1] retain];
212 }
213
214 static inline WOKeyPathComponent *
215 _getComponent(register WOKeyPathAssociation *self, register unsigned _idx)
216 {
217   return (WOKeyPathComponent *)
218     (self->keyPath + (_idx * sizeof(WOKeyPathComponent)));
219 }
220
221 static inline void _freeKeyPathComponent(WOKeyPathComponent *info) {
222   if (info->ckey) {
223     free(info->ckey);
224     info->ckey = NULL;
225   }
226   if ((info->type == WOKeyType_kvc) || (info->type == WOKeyType_binding)) {
227     [info->extra.key release];
228     info->extra.key = nil;
229   }
230   info->object = nil;
231   info->isa    = Nil;
232 }
233
234 static inline void
235 _parseKeyPath(WOKeyPathAssociation *self,NSString *_keyPath)
236 {
237   unsigned keyLen = [_keyPath cStringLength];
238   char *buf;
239   char *cstr;
240   char *tmp;
241
242   buf = malloc(keyLen + 1);
243   cstr = buf;
244   tmp = (char *)cstr;
245   [_keyPath getCString:buf]; buf[keyLen] = '\0';
246   
247   // get number of components
248   self->size = 1;
249   while (*tmp) {
250     if (*tmp == '.') (self->size)++;
251     tmp++;
252   }
253   
254   self->keyPath = calloc(self->size, sizeof(WOKeyPathComponent));
255   
256   // transfer components
257   {
258     unsigned char pos;
259
260     tmp = (char *)cstr;
261     for (pos = 0; pos < self->size; pos++) {
262       WOKeyPathComponent *info = _getComponent(self, pos);
263       
264       // goto end or next '.'
265       while ((*tmp != '\0') && (*tmp != '.'))
266         tmp++;
267       
268       info->keyLen = (tmp - cstr);
269       info->ckey   = malloc(info->keyLen + 4);
270       memcpy(info->ckey, cstr, info->keyLen);
271       info->ckey[info->keyLen] = '\0';
272
273       NSCAssert(strlen(info->ckey) > 0, @"invalid ckey ..");
274       NSCAssert((int)strlen(info->ckey) == (int)info->keyLen, 
275                 @"size and content differ");
276       
277       info->object = nil;
278       info->isa    = Nil;
279       info->type   = WOKeyType_unknown;
280       
281       cstr = tmp + 1;
282       tmp  = (char *)cstr;
283     }
284   }
285   free(buf);
286 }
287   
288 - (id)initWithKeyPath:(NSString *)_keyPath {
289   if ([_keyPath length] < 1) {
290     self = [self autorelease];
291     self = nil;
292     NSLog(@"ERROR: passed invalid keypath (%@) to association !", _keyPath);
293     return nil;
294   }
295   if ((self = [super init])) {
296     _parseKeyPath(self, _keyPath);
297   }
298   return self;
299 }
300 - (id)init {
301   NSLog(@"keypaths can not be created using 'init' ..");
302   [NSException raise:@"InvalidUseOfMethodException"
303                format:@"keypaths can not be created using 'init'"];
304   return nil;
305 }
306
307 - (void)dealloc {
308   if (self->keyPath) {
309     int cnt;
310     
311     for (cnt = 0; cnt < self->size; cnt++)
312       _freeKeyPathComponent(_getComponent(self, cnt));
313     
314     free(self->keyPath);
315   }
316   [super dealloc];
317 }
318
319 /* accessors */
320
321 - (NSString *)keyPath {
322   register unsigned char pos;
323   char     *buffer;
324   unsigned len;
325
326   // get size
327   len = self->size; // size-1 '.' chars and the '\0' char
328   for (pos = 0; pos < self->size; pos++) {
329     WOKeyPathComponent *info = _getComponent(self, pos);
330     len += info->keyLen;
331   }
332   buffer = malloc(len + 4);
333   
334   // transfer contents
335   for (pos = 0, len = 0; pos < self->size; pos++) {
336     WOKeyPathComponent *info = _getComponent(self, pos);
337
338     if (pos != 0) {
339       buffer[len] = '.';
340       len++;
341     }
342     memcpy(&(buffer[len]), info->ckey, info->keyLen);
343     len += info->keyLen;
344   }
345   buffer[len] = '\0';
346
347   return [[[StringClass alloc] initWithCStringNoCopy:buffer
348                                length:len
349                                freeWhenDone:YES]
350                                autorelease];
351 }
352 - (id)initWithString:(NSString *)_s {
353   return [self initWithKeyPath:_s];
354 }
355
356 /* value */
357
358 static inline void _fillInfo(WOKeyPathAssociation *self, id object,
359                              WOKeyPathComponent *info) 
360 {
361   Class clazz      = [object class];
362   BOOL  needRefill = NO;
363   
364   if (info->isFault)
365     needRefill = YES;
366   else if (info->type == WOKeyType_unknown) // first invocation of 'value'
367     needRefill = YES;
368   else if ((info->object == nil) || (object == nil))
369     needRefill = YES;
370   else if (info->isa != clazz)
371     needRefill = YES;
372   else if (info->object != object)
373     needRefill = YES;
374   
375   if (!needRefill)
376     // object is the same, can use cached representation
377     return;
378
379   {
380 #if NeXT_RUNTIME
381     struct objc_method *method = NULL;
382 #else
383     Method_t method = METHOD_NULL;
384 #endif
385     
386     if ((info->type == WOKeyType_kvc) || (info->type == WOKeyType_binding)) {
387       /* release old key */
388       [info->extra.key release];
389       info->extra.key = nil;
390     }
391     
392     info->type    = WOKeyType_unknown;
393     info->retType = _C_ID;
394     
395     if (*(info->ckey) == '^') { /* a binding key */
396       method = class_get_instance_method(clazz, @selector(valueForBinding:));
397       info->type = WOKeyType_binding;
398       info->extra.key =
399         [[StringClass alloc] initWithCString:(info->ckey + 1)];
400     }
401     else {
402       if (object) {
403         if (object_is_instance(object)) {
404           if ([object kvcIsPreferredInKeyPath]) {
405             method = class_get_instance_method(clazz, @selector(valueForKey:));
406             
407             if (method != METHOD_NULL) {
408               info->type      = WOKeyType_kvc;
409               info->extra.key = 
410                 [[StringClass alloc] initWithCString:info->ckey];
411             }
412             else {
413               info->extra.sel.get = sel_get_uid(info->ckey);
414               method = class_get_instance_method(clazz, info->extra.sel.get);
415               if (method != METHOD_NULL)
416                 info->type = WOKeyType_method;
417             }
418           }
419           else {
420             info->extra.sel.get = sel_get_uid(info->ckey);
421             method = class_get_instance_method(clazz, info->extra.sel.get);
422             
423             if (method != METHOD_NULL)
424               info->type = WOKeyType_method;
425             else {
426               method =
427                 class_get_instance_method(clazz, @selector(valueForKey:));
428               if (method != METHOD_NULL) {
429                 info->type      = WOKeyType_kvc;
430                 info->extra.key = 
431                   [[StringClass alloc] initWithCString:info->ckey];
432               }
433             }
434           }
435         }
436         else { /* object is a class */
437           method = class_get_class_method(object, @selector(valueForKey:));
438           if (method != METHOD_NULL) {
439             info->type      = WOKeyType_kvc;
440             info->extra.key = [[StringClass alloc] initWithCString:info->ckey];
441           }
442           else {
443             info->extra.sel.get = sel_get_uid(info->ckey);
444             method =
445               class_get_class_method(*(Class *)object, info->extra.sel.get);
446             if (method != METHOD_NULL) {
447               info->type = WOKeyType_method;
448             }
449           }
450         }
451       }
452     }
453     info->object  = object;
454     info->isa     = [object class];
455     info->isFault = [object isFault];
456     
457     if (method != METHOD_NULL) {
458 #if NeXT_RUNTIME
459       info->access.method = method->method_imp;
460 #else
461       info->access.method = method_get_imp(method);
462 #endif
463       info->retType = *(method->method_types);
464     }
465
466 #if HEAVY_DEBUG
467     NSLog(@"type is %i, key is '%s'", info->type, info->ckey);
468 #endif
469   }
470 }
471
472 - (NSException *)handleGetException:(NSException *)_exception  {
473   static NSString *excKey    = @"exception";
474   static NSString *kpKey     = @"keyPath";
475   static NSString *assocKey  = @"association";
476   static NSString *excName   = @"WOKeyPathException";
477   static NSString *excReason = @"couldn't get value for a keypath component";
478   NSException  *e;
479   NSDictionary *ui;
480   
481   ui = [NSDictionary dictionaryWithObjectsAndKeys:
482                        _exception,     excKey,
483                        [self keyPath], kpKey,
484                        self,           assocKey,
485                      nil];
486   
487   e = [[NSException alloc] initWithName:excName
488                            reason:excReason
489                            userInfo:ui];
490   return e;
491 }
492
493 static WOReturnValueHolder _getComponentValue(WOKeyPathAssociation *self,
494                                               id object,
495                                               WOKeyPathComponent *info)
496 {
497   WOReturnValueHolder retValue;
498   
499 #if DEBUG
500   NSCAssert1(info, @"%s: missing info !", __PRETTY_FUNCTION__);
501 #endif
502   
503   _fillInfo(self, object, info);
504   
505   // execute
506   if (info->type == WOKeyType_method) {
507 #if HEAVY_DEBUG
508     NSLog(@"get key %s of keyPath %@\n"
509           @"  from: 0x%08X[%@]\n"
510           @"  via method (ret %c)", 
511           info->ckey, [self keyPath], object, 
512           NSStringFromClass([object class]), info->retType);
513 #endif
514 #if USE_EXCEPTION_HANDLERS
515     NS_DURING {
516 #endif
517       if ((info->retType == _C_ID) || (info->retType == _C_CLASS)) {
518         retValue.object = info->access.method(object, info->extra.sel.get);
519 #if HEAVY_DEBUG
520         NSLog(@"  got result 0x%08X[%@]: %@", retValue.object,
521               NSStringFromClass([retValue.object class]),
522               retValue.object);
523 #endif
524       }
525       else {
526         switch (info->retType) {
527           case _C_ID:
528           case _C_CLASS:
529             retValue.object = info->access.method(object, info->extra.sel.get);
530             break;
531           case _C_VOID:
532             retValue.object = object;
533             break;
534             
535           case _C_CHR:
536           case _C_UCHR:
537             retValue.c = info->access.ucmethod(object, info->extra.sel.get);
538             break;
539           
540           case _C_INT:
541             retValue.sint = info->access.imethod(object, info->extra.sel.get);
542             break;
543           case _C_UINT:
544             retValue.uint = info->access.uimethod(object, info->extra.sel.get);
545             break;
546
547           case _C_SHT:
548             retValue.ss = info->access.smethod(object, info->extra.sel.get);
549             break;
550           case _C_USHT:
551             retValue.us = info->access.usmethod(object, info->extra.sel.get);
552             break;
553
554           case _C_FLT:
555             retValue.flt = info->access.fmethod(object, info->extra.sel.get);
556             break;
557         
558           case _C_DBL:
559             retValue.dbl = info->access.dmethod(object, info->extra.sel.get);
560             break;
561
562           case _C_CHARPTR:
563             retValue.cstr = info->access.strmethod(object, info->extra.sel.get);
564             break;
565
566           default:
567             NSLog(@"%@: unsupported type '%c' !", self, info->retType);
568             [NSException raise:@"WORuntimeException"
569                          format:
570                          @"in WOKeyPathAssociation %@: unsupported type '%c'",
571                          self, info->retType];
572             break;
573         }
574       }
575 #if USE_EXCEPTION_HANDLERS
576     }
577     NS_HANDLER
578       [[self handleGetException:localException] raise];
579     NS_ENDHANDLER;
580 #endif
581   }
582   else if (info->type == WOKeyType_kvc) {
583 #if HEAVY_DEBUG
584     NSLog(@"get keyPath %@ from %@ via KVC", [self keyPath], object);
585 #endif
586 #if 0
587     NSLog(@"ckey:      %s", info->ckey);
588     NSLog(@"key-class: %s", (*(Class *)info->extra.key)->name);
589     NSLog(@"key:       %@", info->extra.key);
590 #endif
591
592 #if LIB_FOUNDATION_BOEHM_GC
593     [GarbageCollector collectGarbages];
594 #endif
595     
596     retValue.object =
597       info->access.method(object, @selector(valueForKey:), info->extra.key);
598   }
599   else if (info->type == WOKeyType_binding) {
600 #if HEAVY_DEBUG
601     NSLog(@"get keyPath %@ from %@ via binding", [self keyPath], object);
602 #endif
603     
604     retValue.object =
605       info->access.method(object, @selector(valueForBinding:), info->extra.key);
606   }
607   else { // unknown || ivar
608 #if HEAVY_DEBUG
609     NSLog(@"unknown info type for keyPath %@ from %@ !!",
610           [self keyPath], object);
611 #endif
612     retValue.object = nil;
613   }
614
615   return retValue;
616 }
617
618 static inline id _objectify(unsigned char _type, WOReturnValueHolder *_value) {
619   id result = nil;
620
621   //NSLog(@"shall convert value of type '%c'", _type);
622   
623   switch (_type) {
624     case _C_ID:
625     case _C_CLASS:
626       result = _value->object;
627       break;
628       
629     case _C_VOID:
630       result = _value->object;
631       break;
632       
633     case _C_CHR:
634     case _C_UCHR:
635       result = [NumberClass numberWithUnsignedChar:_value->c];
636       break;
637       
638     case _C_INT:
639       result = intNumObj(_value->sint);
640       break;
641     case _C_UINT:
642       result = uintNumObj(_value->uint);
643       break;
644     case _C_SHT:
645       result = [NumberClass numberWithShort:_value->ss];
646       break;
647     case _C_USHT:
648       result = [NumberClass numberWithUnsignedShort:_value->us];
649       break;
650     case _C_FLT:
651       result = [NumberClass numberWithFloat:_value->flt];
652       break;
653     case _C_DBL:
654       result = [NumberClass numberWithDouble:_value->dbl];
655       break;
656
657     case _C_CHARPTR:
658       result = _value->cstr
659         ? [StringClass stringWithCString:_value->cstr]
660         : nil;
661       break;
662             
663     default:
664       NSLog(@"unsupported type '%c' !", _type);
665       [NSException raise:@"WORuntimeException"
666                    format:@"in WOKeyPathAssociation: unsupported type '%c'",
667                      _type];
668       break;
669   }
670
671   //NSLog(@"made %@[0x%08X].", NSStringFromClass([result class]), result);
672   
673   return result;
674 }
675
676 static inline id
677 _getValueN(WOKeyPathAssociation *self, unsigned _count, id root)
678 {
679   register unsigned cnt;
680   id object = root;
681
682   for (cnt = 0; (cnt < _count) && (object != nil); cnt++) {
683     WOKeyPathComponent  *info;
684     WOReturnValueHolder retValue;
685     
686     info     = _getComponent(self, cnt);
687 #if DEBUG
688     NSCAssert1(info, @"%s: missing info !", __PRETTY_FUNCTION__);
689 #endif
690     retValue = _getComponentValue(self, object, info);
691     
692     object = (info->type == WOKeyType_method)
693       ? _objectify(info->retType, &retValue)
694       : retValue.object;
695   }
696
697   //NSLog(@"object %@ for keyPath %@", object, [self keyPath]);
698
699   return object;
700 }
701
702 static inline id _getValue(WOKeyPathAssociation *self, id root) {
703   return _getValueN(self, self->size, root);
704 }
705
706 static id _getOneValue(WOKeyPathAssociation *self, id root) {
707   WOKeyPathComponent  *info;
708   WOReturnValueHolder retValue;
709
710   info     = (WOKeyPathComponent *)self->keyPath;
711   retValue = _getComponentValue(self, root, info);
712   
713   return (info->type == WOKeyType_method)
714     ? _objectify(info->retType, &retValue)
715     : retValue.object;
716 }
717
718 static inline void _getSetSelName(register unsigned char *buf,
719                                   register const unsigned char *_key,
720                                   register unsigned _len) {
721   buf[0] = 's';
722   buf[1] = 'e';
723   buf[2] = 't';
724
725   switch (_len) {
726     case 0: break;
727
728     case 1:
729       buf[3] = _key[0];
730       break;
731     case 2:
732       buf[3] = _key[0]; buf[4] = _key[1];
733       break;
734     case 3:
735       buf[3] = _key[0]; buf[4] = _key[1]; buf[5] = _key[2];
736       break;
737     case 4:
738       buf[3] = _key[0]; buf[4] = _key[1]; buf[5] = _key[2];
739       buf[6] = _key[3]; break;
740     case 5:
741       buf[3] = _key[0]; buf[4] = _key[1]; buf[5] = _key[2];
742       buf[6] = _key[3]; buf[7] = _key[4];
743       break;
744     case 6:
745       buf[3] = _key[0]; buf[4] = _key[1]; buf[5] = _key[2];
746       buf[6] = _key[3]; buf[7] = _key[4]; buf[8] = _key[5];
747       break;
748       
749     default:
750       memcpy(&(buf[3]), _key, _len);
751       break;
752   }
753   buf[3] = toupper(buf[3]);
754   buf[_len + 3] = ':';
755   buf[_len + 4] = '\0';
756 }
757 static inline SEL _getSetSel(register const unsigned char *_key,
758                              register unsigned _len) {
759   char buf[259];
760   _getSetSelName(buf, _key, _len);
761   return sel_get_uid(buf);
762 }
763
764 static BOOL _setValue(WOKeyPathAssociation *self, id _value, id root) {
765   WOKeyPathComponent *info;
766   id object = root;
767   
768   if (self->size > 1)
769     object = _getValueN(self, self->size - 1, root);
770
771   if (object == nil) // nothing to set ..
772     return YES; // receiver == nil isn't an error condition
773
774   info = _getComponent(self, self->size - 1);
775   NSCAssert(info->keyLen < 255, @"keysize to big ..");
776
777   _fillInfo(self, object, info);
778   
779   if (info->type == WOKeyType_method) { // determine set-selector
780     SEL setSel = _getSetSel(info->ckey, info->keyLen);
781       
782     if (![object respondsToSelector:setSel]) {
783 #if 0
784       NSLog(@"%@: Could not set value for key '%s', "
785             @"object %@ doesn't respond to %@.",
786             self, info->ckey, object,
787             setSel ? NSStringFromSelector(setSel) : @"<NULL>");
788 #endif
789       return NO;
790     }
791     else {
792       WOSetMethodType sm;
793
794       if ((sm.method = [object methodForSelector:setSel])) {
795         switch (info->retType) {
796           case _C_CLASS:
797           case _C_ID:
798             sm.omethod(object, setSel, _value);
799             break;
800
801           case _C_CHR:
802             sm.cmethod(object, setSel, [_value charValue]);
803             break;
804           case _C_UCHR:
805             sm.ucmethod(object, setSel, [_value unsignedCharValue]);
806             break;
807
808           case _C_SHT:
809             sm.smethod(object, setSel, [_value shortValue]);
810             break;
811           case _C_USHT:
812             sm.usmethod(object, setSel, [_value unsignedShortValue]);
813             break;
814             
815           case _C_INT:
816             sm.imethod(object, setSel, [_value intValue]);
817             break;
818           case _C_UINT:
819             sm.uimethod(object, setSel, [_value unsignedIntValue]);
820             break;
821
822           case _C_FLT:
823             sm.fmethod(object, setSel, [_value floatValue]);
824             break;
825         
826           case _C_DBL:
827             sm.dmethod(object, setSel, [_value doubleValue]);
828             break;
829
830           case _C_CHARPTR:
831             if (_value == nil)
832               sm.strmethod(object, setSel, NULL);
833             else {
834               unsigned clen;
835               if ((clen = [_value cStringLength]) == 0)
836                 sm.strmethod(object, setSel, "");
837               else {
838                 unsigned char *buf;
839                 buf = malloc(clen + 4);
840                 [_value getCString:buf]; buf[clen] = '\0';
841                 sm.strmethod(object, setSel, buf);
842                 if (buf) free(buf);
843               }
844             }
845             break;
846             
847           default:
848             NSLog(@"%@: cannot set type '%c' yet (key=%s, method=%@) ..",
849                   self, info->retType, info->ckey,
850                   NSStringFromSelector(setSel));
851             [NSException raise:@"WORuntimeException"
852                          format:
853                            @"in WOKeyPathAssociation %@: "
854                            @"cannot set type '%c' yet (key=%s, method=%@)",
855                            self, info->retType, info->ckey,
856                            NSStringFromSelector(setSel)];
857             return NO;
858         }
859         return YES;
860       }
861       else {
862         NSLog(@"%@: did not find method %@ in object %@",
863               self, NSStringFromSelector(setSel), object);
864         return NO;
865       }
866     }
867   }
868   else if (info->type == WOKeyType_kvc) { // takeValue:forKey:..
869     NSCAssert(info->extra.key, @"no key object set ..");
870 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \
871     APPLE_FOUNDATION_LIBRARY
872     if([object isKindOfClass:NSCFDictionaryClass] &&
873        !_CFDictionaryIsMutable((CFDictionaryRef)object))
874         return NO;
875 #endif
876     [object takeValue:_value forKey:info->extra.key];
877     return YES;
878   }
879   else if (info->type == WOKeyType_binding) { // setValue:forBinding:
880     NSCAssert(info->extra.key, @"no key object set ..");
881     [object setValue:_value forBinding:info->extra.key];
882     return YES;
883   }
884   else {
885     NSLog(@"%@: Could not set value for key '%s'.", self, info->ckey);
886     return NO;
887   }
888 }
889
890 - (void)setValue:(id)_value inComponent:(WOComponent *)_component {
891   if (debugOn)
892     NSLog(@"%@: set value %@ component %@", self, _value, _component);
893   
894   _setValue(self, _value, _component);
895 }
896 - (id)valueInComponent:(WOComponent *)_component {
897 #if DEBUG
898   volatile id result;
899   if (debugOn)
900     NSLog(@"%@: get value in component %@", self, _component);
901   
902 #if USE_EXCEPTION_HANDLERS
903   NS_DURING {
904 #endif
905     result = (self->size > 1)
906       ? _getValue(self, _component)
907       : _getOneValue(self, _component);
908 #if USE_EXCEPTION_HANDLERS
909   }
910   NS_HANDLER {
911     fprintf(stderr, "during evaluation of keypath %s:\n  %s\n",
912             [[self description] cString],
913             [[localException description] cString]);
914     fflush(stderr);
915     [localException raise];
916   }
917   NS_ENDHANDLER;
918 #endif
919   return result;
920 #else
921   if (debugOn)
922     NSLog(@"%@: get value in component %@", self, _component);
923   
924   return (self->size > 1)
925     ? _getValue(self, _component)
926     : _getOneValue(self, _component);
927 #endif
928 }
929
930 - (BOOL)isValueConstant {
931   return NO;
932 }
933 - (BOOL)isValueSettable {
934   return YES;
935 }
936
937 /* special values */
938
939 - (void)setUnsignedIntValue:(unsigned int)_value
940   inComponent:(WOComponent *)_wo
941 {
942   WOKeyPathComponent *info;
943   
944   if (debugOn)
945     NSLog(@"%@: set uint value %i in component %@", self, _value, _wo);
946   
947   if (self->size > 1) {
948     _setValue(self, uintNumObj(_value), _wo);
949     return;
950   }
951
952   info = (WOKeyPathComponent *)self->keyPath;
953   NSCAssert(info->keyLen < 255, @"keysize to big ..");
954     
955   _fillInfo(self, _wo, info);
956     
957   if (info->type == WOKeyType_method) { /* determine set-selector */
958     if (info->retType == _C_CHR || info->retType == _C_UCHR ||
959         info->retType == _C_INT || info->retType == _C_UINT) {
960       SEL             setSel;
961       WOSetMethodType sm;
962         
963       setSel = _getSetSel(info->ckey, info->keyLen);
964       sm.method = [_wo methodForSelector:setSel];
965       NSAssert1(sm.method, @"didn't find method for key %s", info->ckey);
966         
967       switch (info->retType) {
968           case _C_CHR: {
969             if (((int)_value < -126) || ((int)_value > 127))
970               NSLog(@"%@: value (%i) out of range for char !", self, _value);
971             sm.cmethod(_wo, setSel, (char)_value);
972             break;
973           }
974           case _C_UCHR: {
975             if ((_value < 0) || (_value > 255))
976               NSLog(@"%@: value (%i) out of range for uchar !", self, _value);
977             sm.ucmethod(_wo, setSel, (unsigned char)_value);
978             break;
979           }
980           case _C_INT: {
981             sm.imethod(_wo, setSel, (int)_value);
982             break;
983           }
984           case _C_UINT: {
985             sm.uimethod(_wo, setSel, (unsigned int)_value);
986             break;
987           }
988
989           default:
990             [NSException raise:@"WORuntimeException"
991                          format:
992                            @"in WOKeyPathAssociation %@: "
993                            @"does not handle type %c",
994                            self, info->retType];
995             break;
996       }
997     }
998     else {
999       // usual setValue
1000         _setValue(self, uintNumObj(_value), _wo);
1001     }
1002     return;
1003   }
1004
1005   if (info->type == WOKeyType_kvc) { // takeValue:forKey:..
1006     NSCAssert(info->extra.key, @"no key object set ..");
1007     [_wo takeValue:uintNumObj(_value) forKey:info->extra.key];
1008     return;
1009   }
1010   if (info->type == WOKeyType_binding) { // setValue:forBinding:
1011     NSCAssert(info->extra.key, @"no key object set ..");
1012     [_wo setValue:uintNumObj(_value) forBinding:info->extra.key];
1013     return;
1014   }
1015   
1016   NSLog(@"%@: Could not set value for key '%s'.", self, info->ckey);
1017 }
1018 - (unsigned int)unsignedIntValueInComponent:(WOComponent *)_component {
1019   WOKeyPathComponent  *info;
1020   WOReturnValueHolder retValue;
1021     
1022   if (debugOn)
1023     NSLog(@"%@: get uint value in component %@", self, _component);
1024   
1025   if (self->size > 1)
1026     return [_getValue(self, _component) unsignedIntValue];
1027
1028   info     = (WOKeyPathComponent *)self->keyPath;
1029   retValue = _getComponentValue(self, _component, info);
1030
1031   if (info->type == WOKeyType_method) {
1032     switch (info->retType) {
1033       case _C_UINT: return retValue.uint;
1034       case _C_INT:  return retValue.sint;
1035       case _C_UCHR: return retValue.c;
1036       case _C_CHR:  return retValue.c;
1037       case _C_SHT:  return retValue.ss;
1038       case _C_USHT: return retValue.us;
1039     }
1040     return [_objectify(info->retType, &retValue) unsignedIntValue];
1041   }
1042
1043 #if 0
1044   NSLog(@"ret value object for key '%s' is 0x%08X",
1045         info->ckey, retValue.object);
1046   NSLog(@"ret value object class is %@", [retValue.object class]);
1047 #endif
1048   return [retValue.object unsignedIntValue];
1049 }
1050
1051 - (void)setIntValue:(int)_value inComponent:(WOComponent *)_wo {
1052   WOKeyPathComponent *info;
1053   
1054   if (debugOn)
1055     NSLog(@"%@: set int value %i in component %@", self, _value, _wo);
1056   
1057   if (self->size > 1) {
1058     _setValue(self, intNumObj(_value), _wo);
1059     return;
1060   }
1061
1062   info = (WOKeyPathComponent *)self->keyPath;
1063   NSCAssert(info->keyLen < 255, @"keysize to big ..");
1064     
1065   _fillInfo(self, _wo, info);
1066     
1067   if (info->type == WOKeyType_method) { // determine set-selector
1068       if (info->retType == _C_CHR || info->retType == _C_UCHR ||
1069           info->retType == _C_INT || info->retType == _C_UINT) {
1070         SEL             setSel;
1071         WOSetMethodType sm;
1072         
1073         setSel = _getSetSel(info->ckey, info->keyLen);
1074         sm.method = [_wo methodForSelector:setSel];
1075         NSAssert1(sm.method, @"didn't find method for key %s", info->ckey);
1076         
1077         switch (info->retType) {
1078           case _C_CHR: {
1079             if (((int)_value < -126) || ((int)_value > 127))
1080               NSLog(@"%@: value (%i) out of range for char !", self, _value);
1081             sm.cmethod(_wo, setSel, (char)_value);
1082             break;
1083           }
1084           case _C_UCHR: {
1085             if ((_value < 0) || (_value > 255))
1086               NSLog(@"%@: value (%i) out of range for uchar !", self, _value);
1087             sm.ucmethod(_wo, setSel, (unsigned char)_value);
1088             break;
1089           }
1090           case _C_INT: {
1091             sm.imethod(_wo, setSel, (int)_value);
1092             break;
1093           }
1094           case _C_UINT: {
1095             sm.uimethod(_wo, setSel, (unsigned int)_value);
1096             break;
1097           }
1098
1099           default:
1100             [NSException raise:@"WORuntimeException"
1101                          format:
1102                            @"in WOKeyPathAssociation %@: "
1103                            @"does not handle type %c",
1104                            self, info->retType];
1105             break;
1106         }
1107       }
1108       else {
1109         // usual setValue
1110         _setValue(self, intNumObj(_value), _wo);
1111       }
1112       return;
1113   }
1114
1115   if (info->type == WOKeyType_kvc) { // takeValue:forKey:
1116     NSCAssert(info->extra.key, @"no key object set ..");
1117     [_wo takeValue:intNumObj(_value) forKey:info->extra.key];
1118     return;
1119   }
1120   
1121   if (info->type == WOKeyType_binding) { // setValue:forBinding:
1122     NSCAssert(info->extra.key, @"no key object set ..");
1123     [_wo setValue:intNumObj(_value) forBinding:info->extra.key];
1124     return;
1125   }
1126
1127   NSLog(@"%@: Could not set value for key '%s'.", self, info->ckey);
1128 }
1129 - (int)intValueInComponent:(WOComponent *)_component {
1130   WOKeyPathComponent  *info;
1131   WOReturnValueHolder retValue;
1132
1133   if (debugOn)
1134     NSLog(@"%@: get int value in component %@", self, _component);
1135   
1136   if (self->size > 1)
1137     return [_getValue(self, _component) intValue];
1138
1139   info     = (WOKeyPathComponent *)self->keyPath;
1140   retValue = _getComponentValue(self, _component, info);
1141
1142   if (info->type != WOKeyType_method)
1143     return [retValue.object intValue];
1144
1145   switch (info->retType) {
1146     case _C_UINT: return retValue.uint;
1147     case _C_INT:  return retValue.sint;
1148     case _C_UCHR: return retValue.c;
1149     case _C_CHR:  return retValue.c;
1150     case _C_SHT:  return retValue.ss;
1151     case _C_USHT: return retValue.us;
1152     default:      return [_objectify(info->retType, &retValue) intValue];
1153   }
1154 }
1155
1156 - (void)setBoolValue:(BOOL)_value inComponent:(WOComponent *)_wo {
1157   WOKeyPathComponent *info;
1158   
1159   if (debugOn)
1160     NSLog(@"%@: set bool value %i in component %@", self, _value, _wo);
1161   
1162   if (self->size > 1) {
1163     _setValue(self, [NumberClass numberWithBool:_value], _wo);
1164     return;
1165   }
1166
1167   info = (WOKeyPathComponent *)self->keyPath;
1168   NSCAssert(info->keyLen < 255, @"keysize to big ..");
1169     
1170   _fillInfo(self, _wo, info);
1171     
1172   if (info->type == WOKeyType_method) { // determine set-selector
1173       if (info->retType == _C_CHR || info->retType == _C_UCHR ||
1174           info->retType == _C_INT || info->retType == _C_UINT) {
1175         SEL             setSel;
1176         WOSetMethodType sm;
1177         
1178         setSel = _getSetSel(info->ckey, info->keyLen);
1179         sm.method = [_wo methodForSelector:setSel];
1180         NSAssert1(sm.method, @"didn't find method for key %s", info->ckey);
1181         
1182         switch (info->retType) {
1183           case _C_CHR: {
1184             sm.cmethod(_wo, setSel, (char)_value);
1185             break;
1186           }
1187           case _C_UCHR: {
1188             sm.ucmethod(_wo, setSel, (unsigned char)_value);
1189             break;
1190           }
1191           case _C_INT: {
1192             sm.imethod(_wo, setSel, (int)_value);
1193             break;
1194           }
1195           case _C_UINT: {
1196             sm.uimethod(_wo, setSel, (unsigned int)_value);
1197             break;
1198           }
1199
1200           default:
1201             [NSException raise:@"WORuntimeException"
1202                          format:
1203                            @"in WOKeyPathAssociation %@: "
1204                            @"does not handle type %c",
1205                            self, info->retType];
1206             break;
1207         }
1208       }
1209       else {
1210         // usual setValue
1211         _setValue(self, [NumberClass numberWithBool:_value], _wo);
1212       }
1213   }
1214   else if (info->type == WOKeyType_kvc) { // takeValue:forKey:
1215       NSCAssert(info->extra.key, @"no key object set ..");
1216       [_wo takeValue:[NumberClass numberWithBool:_value]
1217            forKey:info->extra.key];
1218   }
1219   else if (info->type == WOKeyType_binding) { // setValue:forBinding:
1220       NSCAssert(info->extra.key, @"no key object set ..");
1221       [_wo setValue:[NumberClass numberWithBool:_value]
1222            forBinding:info->extra.key];
1223   }
1224   else {
1225       NSLog(@"%@: Could not set value for key '%s'.", self, info->ckey);
1226   }
1227 }
1228 - (BOOL)boolValueInComponent:(WOComponent *)_component {
1229   if (debugOn)
1230     NSLog(@"%@: get bool value in component %@", self, _component);
1231   
1232   if (self->size > 1)
1233     return [_getValue(self, _component) boolValue];
1234   else {
1235     WOKeyPathComponent  *info;
1236     WOReturnValueHolder retValue;
1237
1238     info     = (WOKeyPathComponent *)self->keyPath;
1239     retValue = _getComponentValue(self, _component, info);
1240
1241     if (info->type == WOKeyType_method) {
1242       switch (info->retType) {
1243         case _C_UINT: return retValue.uint;
1244         case _C_INT:  return retValue.sint;
1245         case _C_UCHR: return retValue.c;
1246         case _C_CHR:  return retValue.c;
1247         case _C_SHT:  return retValue.ss;
1248         case _C_USHT: return retValue.us;
1249
1250         default:
1251           return [_objectify(info->retType, &retValue) boolValue];
1252       }
1253     }
1254     else
1255       return [retValue.object boolValue];
1256   }
1257 }
1258
1259 - (void)setStringValue:(NSString *)_value inComponent:(WOComponent *)_wo {
1260   if (debugOn)
1261     NSLog(@"%@: set string value '%@' in component %@", self, _value, _wo);
1262   
1263   _setValue(self, _value, _wo);
1264 }
1265 - (NSString *)stringValueInComponent:(WOComponent *)_component {
1266   WOKeyPathComponent  *info;
1267   WOReturnValueHolder retValue;
1268     
1269   if (debugOn)
1270     NSLog(@"%@: get string value in component %@", self, _component);
1271   
1272   if (self->size > 1)
1273     return [_getValue(self, _component) stringValue];
1274
1275   info     = (WOKeyPathComponent *)self->keyPath;
1276   retValue = _getComponentValue(self, _component, info);
1277
1278   if (info->type != WOKeyType_method)
1279     return [retValue.object stringValue];
1280
1281   switch (info->retType) {
1282     case _C_UINT:
1283       if (IS_UNUMSTR(retValue.uint)) return numStrings[retValue.uint];
1284       return [StringClass stringWithFormat:@"%u", retValue.uint];
1285     case _C_INT:
1286       if (IS_NUMSTR(retValue.sint)) return numStrings[retValue.sint];
1287       return [StringClass stringWithFormat:@"%d", retValue.sint];
1288     case _C_UCHR:
1289       if (IS_UNUMSTR(retValue.c)) return numStrings[retValue.c];
1290       return [StringClass stringWithFormat:@"%d", (int)retValue.c];
1291     case _C_CHR:
1292       if (IS_NUMSTR((char)retValue.c)) return numStrings[retValue.c];
1293       return [StringClass stringWithFormat:@"%d", (int)retValue.c];
1294     case _C_SHT:
1295       if (IS_NUMSTR(retValue.ss)) return numStrings[retValue.ss];
1296       return [StringClass stringWithFormat:@"%d", (int)retValue.ss];
1297     case _C_USHT:
1298       if (IS_UNUMSTR(retValue.us)) return numStrings[retValue.us];
1299       return [StringClass stringWithFormat:@"%d", (int)retValue.us];
1300     
1301 #if 0      
1302     case _C_FLT:
1303       return [StringClass stringWithFormat:@"%0.7g", retValue.flt];
1304 #endif
1305     default:
1306       return [_objectify(info->retType, &retValue) stringValue];
1307   }
1308 }
1309
1310 /* NSCoding */
1311
1312 - (void)encodeWithCoder:(NSCoder *)_coder {
1313   [_coder encodeObject:[self keyPath]];
1314 }
1315 - (id)initWithCoder:(NSCoder *)_coder {
1316   return [self initWithKeyPath:[_coder decodeObject]];
1317 }
1318
1319 /* NSCopying */
1320
1321 - (id)copyWithZone:(NSZone *)_zone {
1322   /* keypath associations are immutable */
1323   return [self retain];
1324 }
1325
1326 /* description */
1327
1328 - (NSString *)description {
1329   return [StringClass stringWithFormat:@"<%@[0x%08X]: keyPath=%@>",
1330                         NSStringFromClass([self class]), self,
1331                         [self keyPath]];
1332 }
1333
1334 @end /* WOKeyPathAssociation */