2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE 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
11 SOPE 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.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include <NGObjWeb/WOComponent.h>
23 #include "WOComponent+private.h"
24 #include "NSObject+WO.h"
25 #include <NGObjWeb/WODynamicElement.h>
26 #include "WOContext+private.h"
27 #include "WOElement+private.h"
28 #include <NGObjWeb/WOComponentDefinition.h>
29 #include <NGObjWeb/WOResourceManager.h>
30 #include <NGObjWeb/WOApplication.h>
31 #include <NGObjWeb/WOResponse.h>
32 #include "WOComponentFault.h"
34 #include <NGExtensions/NGBundleManager.h>
35 #include <EOControl/EOControl.h>
36 #include <NGExtensions/NSString+Ext.h>
38 @interface WOContext(ComponentStackCount)
39 - (unsigned)componentStackCount;
42 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
43 @interface NSObject(Miss)
44 - (id)notImplemented:(SEL)cmd;
48 #if !LIB_FOUNDATION_LIBRARY
49 # define NG_USE_KVC_FALLBACK 1
52 @implementation WOComponent
54 static Class NSDateClass = Nil;
55 static Class WOComponentClass = Nil;
57 static NGLogger *perfLogger = nil;
59 static BOOL debugOn = NO;
60 static BOOL debugComponentAwake = NO;
61 static BOOL debugTemplates = NO;
62 static BOOL abortOnAwakeComponentInCtxDealloc = NO;
63 static BOOL abortOnMissingCtx = NO;
64 static BOOL wakeupPageOnCreation = NO;
67 // TODO: is really v4 for baseURL/cycleContext ivar changes
68 return [super version] + 0 /* v2 */;
73 static BOOL didInit = NO;
78 NSAssert2([super version] == 2,
79 @"invalid superclass (%@) version %i !",
80 NSStringFromClass([self superclass]), [super version]);
82 ud = [NSUserDefaults standardUserDefaults];
83 lm = [NGLoggerManager defaultLoggerManager];
85 WOComponentClass = [WOComponent class];
86 NSDateClass = [NSDate class];
87 perfLogger = [lm loggerForDefaultKey:@"WOProfileElements"];
88 debugOn = [WOApplication isDebuggingEnabled];
89 debugComponentAwake = [ud boolForKey:@"WODebugComponentAwake"];
90 abortOnAwakeComponentInCtxDealloc =
91 [ud boolForKey:@"WOCoreOnAwakeComponentInCtxDealloc"];
95 if ((self = [super init])) {
96 NSNotificationCenter *nc;
97 WOComponentDefinition *cdef;
99 if ((cdef = (id)self->wocVariables)) {
100 // HACK CD, see WOComponentDefinition
101 self->wocVariables = nil;
104 if (self->wocName == nil)
105 self->wocName = [NSStringFromClass([self class]) copy];
107 [self setCachingEnabled:[[self application] isCachingEnabled]];
109 /* finish initialization */
112 [cdef _finishInitializingComponent:self];
113 [cdef release]; cdef = nil;
115 #if !APPLE_FOUNDATION_LIBRARY && !NeXT_Foundation_LIBRARY
117 /* this is triggered by Publisher on MacOSX */
118 [self debugWithFormat:
119 @"Note: got no component definition according to HACK CD"];
123 /* add to notification center */
125 nc = [NSNotificationCenter defaultCenter];
127 [nc addObserver:self selector:@selector(_sessionWillDealloc:)
128 name:@"WOSessionWillDeallocate" object:nil];
130 [nc addObserver:self selector:@selector(_contextWillDealloc:)
131 name:@"WOContextWillDeallocate" object:nil];
135 - (id)initWithContext:(WOContext *)_ctx {
136 [self _setContext:_ctx];
137 if ((self = [self init])) {
139 [self ensureAwakeInContext:self->context];
141 [self warnWithFormat:
142 @"no context given to -initWithContext: ..."];
149 [[NSNotificationCenter defaultCenter] removeObserver:self];
151 [[self->subcomponents allValues]
152 makeObjectsPerformSelector:@selector(setParent:)
154 [self->subcomponents release];
156 [self->wocClientObject release];
157 [self->wocBindings release];
158 [self->wocVariables release];
159 [self->wocName release];
160 [self->wocBaseURL release];
164 static inline void _setExtraVar(WOComponent *self, NSString *_key, id _obj) {
166 if (self->wocVariables == nil)
167 self->wocVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
169 [self->wocVariables setObject:_obj forKey:_key];
172 [self->wocVariables removeObjectForKey:_key];
174 static inline id _getExtraVar(WOComponent *self, NSString *_key) {
175 return [self->wocVariables objectForKey:_key];
180 - (void)_sessionWillDealloc:(NSNotification *)_notification {
182 NSAssert(_notification, @"missing valid session arg ...");
185 if (self->session == nil) {
186 /* component isn't interested in session anymore anyway ... */
189 if (self->session != [_notification object])
190 /* not the component's context ... */
194 [self debugWithFormat:@"resetting sn/ctx because session will dealloc .."];
197 if (self->componentFlags.isAwake) {
198 [self warnWithFormat:
199 @"session will dealloc, but component 0x%08X is awake (ctx=%@) !",
200 self, self->context];
201 [self _sleepWithContext:self->context];
205 [self _setContext:nil];
207 - (void)_contextWillDealloc:(NSNotification *)_notification {
209 NSAssert(_notification, @"missing valid notification arg ...");
212 if (self->context == nil)
213 /* component isn't interested in context anyway ... */
215 if (![[self->context contextID] isEqualToString:[_notification object]])
216 /* not the component's context ... */
220 [self debugWithFormat:@"resetting sn/ctx because context will dealloc .."];
223 if (self->componentFlags.isAwake) {
225 Note: this is not necessarily a problem, no specific reason to log
228 [self debugWithFormat:
229 @"context %@ will dealloc, but component is awake in ctx %@!",
230 [_notification object], [self->context contextID]];
231 if (abortOnAwakeComponentInCtxDealloc)
234 [self _sleepWithContext:nil];
237 [self _setContext:nil];
247 if (self->componentFlags.isAwake) {
248 [self warnWithFormat:
249 @"component should not be awake if sleep is called !"];
251 if (self->context == nil) {
252 [self warnWithFormat:
253 @"context should not be nil if sleep is called !"];
257 self->componentFlags.isAwake = 0;
258 [self _setContext:nil];
259 self->application = nil;
263 - (void)ensureAwakeInContext:(WOContext *)_ctx {
265 NSAssert1(_ctx, @"missing context for awake (component=%@) ...", self);
268 if (debugComponentAwake)
269 [self debugWithFormat:@"0x%08X ensureAwakeInContext:0x%08X", self, _ctx];
271 if (self->context == nil) [self _setContext:_ctx];
272 if (self->application == nil) self->application = [_ctx application];
274 if ((self->session == nil) && [_ctx hasSession])
275 self->session = [_ctx session];
277 self->componentFlags.isAwake = 1;
278 [_ctx _addAwakeComponent:self]; /* ensure that sleep is called */
280 /* awake subcomponents */
282 NSEnumerator *children;
285 children = [self->subcomponents objectEnumerator];
286 while ((child = [children nextObject]))
287 [child _awakeWithContext:_ctx];
293 - (void)_awakeWithContext:(WOContext *)_ctx {
294 if (self->componentFlags.isAwake)
297 [self ensureAwakeInContext:_ctx];
299 - (void)_sleepWithContext:(WOContext *)_ctx {
300 if (debugComponentAwake)
301 [self debugWithFormat:@"0x%08X _sleepWithContext:0x%08X", self, _ctx];
303 if (_ctx != self->context) {
304 if ((self->context != nil) && (_ctx != nil)) {
305 /* component is active in different context ... */
306 [self warnWithFormat:
307 @"sleep context mismatch (own=0x%08X vs given=0x%08X)",
308 self->context, _ctx];
313 if (self->componentFlags.isAwake) {
315 Sleep all child components, this is necessary to ensure some ordering
316 in the sleep calls. All awake components are put to sleep in any case
317 by the WOContext destructor.
319 NSEnumerator *children;
322 children = [self->subcomponents objectEnumerator];
323 self->componentFlags.isAwake = 0;
325 while ((child = [children nextObject]))
326 [child _sleepWithContext:_ctx];
330 [self _setContext:nil];
331 self->application = nil;
338 return self->wocName;
340 - (NSString *)frameworkName {
341 return [[NGBundle bundleForClass:[self class]] bundleName];
344 NSArray *languages = nil;
346 #if 0 // the component might not yet be awake !
347 languages = [[self context] resourceLookupLanguages];
350 return [[self resourceManager]
351 pathToComponentNamed:[self name]
352 inFramework:[self frameworkName]
353 languages:languages];
355 - (void)setBaseURL:(NSURL *)_url {
356 ASSIGNCOPY(self->wocBaseURL, _url);
361 if (self->wocBaseURL)
362 return self->wocBaseURL;
364 url = [(WOApplication *)[self application] baseURL];
366 [[NSURL URLWithString:@"WebServerResources" relativeToURL:url] copy];
367 return self->wocBaseURL;
370 - (NSString *)componentActionURLForContext:(WOContext *)_ctx {
371 return [@"/" stringByAppendingString:[self name]];
374 - (WOApplication *)application {
375 if (self->application == nil)
376 return (self->application = [WOApplication application]);
377 return self->application;
380 - (id)existingSession {
382 return self->session;
384 if ([[self context] hasSession])
385 return [self session];
389 - (WOSession *)session {
390 if (self->session == nil) {
391 if ((self->session = [[self context] session]) == nil) {
392 [self debugWithFormat:@"could not get session object from context %@",
397 if (self->session == nil)
398 [self warnWithFormat:@"missing session for component!"];
400 return self->session;
403 - (void)_setContext:(WOContext *)_ctx {
404 self->context = _ctx;
406 - (WOContext *)context {
408 return self->context;
410 [self debugWithFormat:
411 @"missing context in component 0x%08X (component%s)",
413 self->componentFlags.isAwake ? " is awake" : " is not awake"];
414 if (abortOnMissingCtx) {
415 [self errorWithFormat:@"aborting, because ctx is missing !"];
419 if (self->application == nil)
420 self->application = [WOApplication application];
421 [self _setContext:[self->application context]];
423 if (self->context == nil)
424 [self warnWithFormat:@"could not determine context object!"];
426 return self->context;
430 return [[self context] hasSession];
433 - (void)setCachingEnabled:(BOOL)_flag {
434 self->componentFlags.reloadTemplates = _flag ? NO : YES;
436 - (BOOL)isCachingEnabled {
437 return (self->componentFlags.reloadTemplates == NO) ? YES : NO;
440 - (WOComponent *)pageWithName:(NSString *)_name {
442 WOResourceManager *rm;
443 WOComponent *component;
445 languages = [[self context] resourceLookupLanguages];
446 rm = [self resourceManager];
449 Note: this API is somewhat broken since the component expects the
450 -initWithContext: message for initialization yet we pass no
453 component = [rm pageWithName:_name languages:languages];
455 // Note: should we call ensureAwakeInContext or is this to early ?
456 // probably the component should be woken up if it enters the ctx.
457 // If we create a page but never render it, we may get a warning
458 // that a context will dealloc but the page is active (yet not awake)
459 // Note: awake is not the same like "has context"! A component can have a
460 // context without being awake - maybe we need an additional method
461 // to hook up a component but the awake list
462 if (wakeupPageOnCreation)
463 [component ensureAwakeInContext:[self context]];
467 - (NSString *)stringForKey:(NSString *)_key
468 inTableNamed:(NSString *)_tableName
469 withDefaultValue:(NSString *)_default
474 langs = [[self context] resourceLookupLanguages];
476 return [[[self application]
479 inTableNamed:_tableName
480 withDefaultValue:_default
484 - (void)setName:(NSString *)_name {
485 if (![_name isNotNull])
486 [self warnWithFormat:@"setting 'nil' name on component!"];
488 ASSIGNCOPY(self->wocName, _name);
491 - (void)setBindings:(NSDictionary *)_bindings {
492 // this is _very_ private and used by WOComponentReference
493 ASSIGNCOPY(self->wocBindings, _bindings);
495 - (NSDictionary *)_bindings {
497 return self->wocBindings;
500 - (void)setSubComponents:(NSDictionary *)_dictionary {
501 ASSIGNCOPY(self->subcomponents, _dictionary);
503 - (NSDictionary *)_subComponents {
505 return self->subcomponents;
508 - (void)setParent:(WOComponent *)_parent {
509 self->parentComponent = _parent;
511 - (WOComponent *)parent {
512 return self->parentComponent;
515 /* language change */
517 - (void)languageArrayDidChange {
522 - (NSString *)elementID {
528 - (id<WOActionResults>)redirectToLocation:(id)_loc {
529 WOContext *ctx = [self context];
536 if ((r = [ctx response]) == nil)
537 r = [[[WOResponse alloc] init] autorelease];
539 if ([_loc isKindOfClass:[NSURL class]])
540 target = [_loc absoluteString];
542 _loc = [_loc stringValue];
543 if ([_loc isAbsoluteURL])
545 else if ([_loc isAbsolutePath])
548 target = [[ctx request] uri];
550 // TODO: check whether the algorithm is correct
551 if (![target hasSuffix:@"/"])
552 target = [target stringByDeletingLastPathComponent];
553 target = [target stringByAppendingPathComponent:_loc];
559 [r setStatus:302 /* moved */];
560 [r setHeader:target forKey:@"location"];
564 - (void)setResourceManager:(WOResourceManager *)_rm {
565 _setExtraVar(self, @"__worm", _rm);
567 - (WOResourceManager *)resourceManager {
568 WOResourceManager *rm;
571 if ((rm = _getExtraVar(self, @"__worm")))
574 /* ask parent component ... */
575 if ((p = [self parent])) {
576 NSAssert2(p != self, @"parent component == component !!! (%@ vs %@)",
578 if ((rm = [p resourceManager]))
582 /* ask application ... */
583 return [[self application] resourceManager];
586 - (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_ext {
587 NSFileManager *fm = [NSFileManager defaultManager];
588 NSEnumerator *languages = nil;
589 NSString *language = nil;
590 BOOL isDirectory = NO;
591 NSString *cpath = [self path];
593 if (_ext) _name = [_name stringByAppendingPathExtension:_ext];
596 [self warnWithFormat:@"no path set in component %@", [self name]];
599 if (![fm fileExistsAtPath:cpath isDirectory:&isDirectory]) {
600 [self warnWithFormat:@"component directory %@ does not exist !", cpath];
604 [self warnWithFormat:@"component path %@ is not a directory !", cpath];
608 // check in language projects
610 languages = [[(WOSession *)[self session] languages] objectEnumerator];
611 while ((language = [languages nextObject])) {
612 language = [[cpath stringByAppendingPathComponent:
613 [language stringByAppendingPathExtension:@"lproj"]]
614 stringByAppendingPathExtension:_name];
616 if ([fm fileExistsAtPath:language])
620 // check in component
621 cpath = [cpath stringByAppendingPathComponent:_name];
622 if ([fm fileExistsAtPath:cpath])
630 - (WOElement *)templateWithHTMLString:(NSString *)_html
631 declarationString:(NSString *)_wod
632 languages:(NSArray *)_languages
634 return [self notImplemented:_cmd];
636 - (WOElement *)templateWithHTMLString:(NSString *)_html
637 declarationString:(NSString *)_wod
640 return [self templateWithHTMLString:_html
641 declarationString:_wod
642 languages:[(WOSession *)[self session] languages]];
645 - (WOElement *)templateWithName:(NSString *)_name {
646 WOResourceManager *resourceManager;
650 if ((resourceManager = [self resourceManager]) == nil) {
651 [self errorWithFormat:@"%s: could not determine resource manager !",
652 __PRETTY_FUNCTION__];
656 languages = [[self context] resourceLookupLanguages];
657 tmpl = [resourceManager templateWithName:_name languages:languages];
659 if (debugTemplates) [self debugWithFormat:@"found template: %@", tmpl];
663 - (void)setTemplate:(id)_template {
665 WO has private API for this:
666 - (void)setTemplate:(WOElement *)template;
667 As mentioned in the OmniGroup WO mailing list ...
669 _setExtraVar(self, @"__wotemplate", _template);
671 - (WOElement *)_woComponentTemplate {
674 if ((element = _getExtraVar(self, @"__wotemplate")))
677 return [self templateWithName:[self name]];
680 /* child components */
682 - (WOComponent *)childComponentWithName:(NSString *)_name {
685 child = [self->subcomponents objectForKey:_name];
686 if ([child isComponentFault]) {
687 NSMutableDictionary *tmp;
689 child = [child resolveWithParent:self];
691 [self warnWithFormat:@"Could not resolve component fault: %@", _name];
695 tmp = [self->subcomponents mutableCopy];
696 [tmp setObject:child forKey:_name];
697 [self->subcomponents release]; self->subcomponents = nil;
698 self->subcomponents = [tmp copy];
699 [tmp release]; tmp = nil;
704 - (BOOL)synchronizesVariablesWithBindings {
708 - (void)setValue:(id)_value forBinding:(NSString *)_name {
711 WODynamicElement *content;
713 ctx = [self context];
714 parent = [ctx parentComponent];
715 content = [ctx componentContent];
718 parent = [self parent];
719 [self warnWithFormat:@"tried to set value of binding '%@' in component "
720 @"'%@' without parent component (parent is '%@') !",
721 _name, [self name], [parent name]];
724 [[self retain] autorelease];
725 [[content retain] autorelease];
727 WOContext_leaveComponent(ctx, self);
728 [[self->wocBindings objectForKey:_name] setValue:_value inComponent:parent];
729 WOContext_enterComponent(ctx, self, content);
731 - (id)valueForBinding:(NSString *)_name {
734 WODynamicElement *content;
737 ctx = [self context];
738 parent = [ctx parentComponent];
739 content = [ctx componentContent];
742 parent = [self parent];
743 [self warnWithFormat:@"tried to retrieve value of binding '%@' in"
744 @" component '%@' without parent component (parent is '%@') !",
745 _name, [self name], [parent name]];
748 [[self retain] autorelease];
749 [[content retain] autorelease];
751 WOContext_leaveComponent(ctx, self);
752 value = [[self->wocBindings objectForKey:_name] valueInComponent:parent];
753 WOContext_enterComponent(ctx, self, content);
758 - (BOOL)hasBinding:(NSString *)_name {
759 return ([self->wocBindings objectForKey:_name] != nil) ? YES : NO;
762 - (BOOL)canSetValueForBinding:(NSString *)_name {
763 WOAssociation *binding;
765 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
768 return [binding isValueSettable];
770 - (BOOL)canGetValueForBinding:(NSString *)_name {
771 WOAssociation *binding;
773 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
779 - (id)performParentAction:(NSString *)_name {
782 WODynamicElement *content;
786 ctx = [self context];
787 parent = [ctx parentComponent];
788 content = [ctx componentContent];
789 action = NSSelectorFromString(_name);
791 if (parent == nil) return nil;
792 if (action == NULL) return nil;
794 NSAssert(parent != self, @"parent component equals current component");
796 if (![parent respondsToSelector:action]) {
797 [self debugWithFormat:@"parent %@ doesn't respond to %@",
798 [parent name], _name];
802 self = [self retain];
804 WOContext_leaveComponent(ctx, self);
805 *(&result) = [parent performSelector:action];
806 WOContext_enterComponent(ctx, self, content);
810 [localException raise];
820 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
824 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
825 WOElement *template = nil;
827 NSAssert1(self->componentFlags.isAwake,
828 @"component %@ is not awake !", self);
830 [self _setContext:_ctx];
831 template = [self _woComponentTemplate];
836 if (template->takeValues) {
837 template->takeValues(template,
838 @selector(takeValuesFromRequest:inContext:),
842 [template takeValuesFromRequest:_req inContext:_ctx];
845 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
846 WOElement *template = nil;
849 NSAssert1(self->componentFlags.isAwake, @"component %@ is not awake!", self);
851 [self _setContext:_ctx];
852 template = [self _woComponentTemplate];
853 result = [template invokeActionForRequest:_req inContext:_ctx];
857 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
858 WOElement *template = nil;
859 NSTimeInterval st = 0.0;
861 NSAssert1(self->componentFlags.isAwake,
862 @"component %@ is not awake !", self);
864 if (self->context != _ctx) {
865 [self debugWithFormat:@"%s: component ctx != ctx (%@ vs %@)",
866 __PRETTY_FUNCTION__, self->context, _ctx];
870 [self _setContext:_ctx];
872 if ((template = [self _woComponentTemplate]) == nil) {
874 [self debugWithFormat:@"component has no template (rm=%@).",
875 [self resourceManager]];
881 st = [[NSDateClass date] timeIntervalSince1970];
883 if (template->appendResponse) {
884 template->appendResponse(template,
885 @selector(appendToResponse:inContext:),
889 [template appendToResponse:_response inContext:_ctx];
894 diff = [[NSDateClass date] timeIntervalSince1970] - st;
896 for (i = [_ctx componentStackCount]; i >= 0; i--)
899 [perfLogger logWithFormat:@"Template %@ (comp %@): %0.3fs\n",
906 /* WOActionResults */
908 - (WOResponse *)generateResponse {
909 WOResponse *response = nil;
910 WOContext *ctx = nil;
913 ctx = [self context];
914 ctxID = [ctx contextID];
915 response = [WOResponse responseWithRequest:[ctx request]];
918 [self debugWithFormat:@"missing ctx-id for context %@", ctx];
922 [ctx deleteAllElementIDComponents];
923 [ctx appendElementIDComponent:ctxID];
925 WOContext_enterComponent(ctx, self, nil);
926 [self appendToResponse:response inContext:ctx];
927 WOContext_leaveComponent(ctx, self);
929 [ctx deleteLastElementIDComponent];
934 if ([[[ctx request] method] isEqualToString:@"HEAD"])
935 [response setContent:[NSData data]];
938 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
939 if ([[ctx application] isPageRefreshOnBacktrackEnabled])
940 [response disableClientCaching];
947 - (void)encodeWithCoder:(NSCoder *)_coder {
948 BOOL doesReloadTemplates = self->componentFlags.reloadTemplates;
950 [_coder encodeObject:self->wocBindings];
951 [_coder encodeObject:self->wocName];
952 [_coder encodeConditionalObject:self->parentComponent];
953 [_coder encodeObject:self->subcomponents];
954 [_coder encodeObject:self->wocVariables];
955 [_coder encodeConditionalObject:self->session];
956 [_coder encodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
958 - (id)initWithCoder:(NSCoder *)_decoder {
959 if ((self = [super init])) {
960 BOOL doesReloadTemplates = YES;
962 self->wocBindings = [[_decoder decodeObject] retain];
963 self->wocName = [[_decoder decodeObject] retain];
964 self->parentComponent = [_decoder decodeObject]; // non-retained
965 self->subcomponents = [[_decoder decodeObject] retain];
966 self->wocVariables = [[_decoder decodeObject] retain];
967 self->session = [_decoder decodeObject]; // non-retained
969 [_decoder decodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
970 [self setCachingEnabled:!doesReloadTemplates];
975 /* component variables */
977 - (BOOL)isStateless {
981 [self->wocVariables removeAllObjects];
984 - (void)setObject:(id)_obj forKey:(NSString *)_key {
985 _setExtraVar(self, _key, _obj);
987 - (id)objectForKey:(NSString *)_key {
988 return _getExtraVar(self, _key);
990 - (NSDictionary *)variableDictionary {
991 return self->wocVariables;
994 - (BOOL)logComponentVariableCreations {
995 /* only if we have a subclass, we can store values in ivars ... */
996 return (self->isa != WOComponentClass) ? YES : NO;
999 #if !NG_USE_KVC_FALLBACK /* only override on libFoundation */
1001 - (void)takeValue:(id)_value forKey:(NSString *)_key {
1002 if (WOSetKVCValueUsingMethod(self, _key, _value)) {
1006 if (WOGetKVCGetMethod(self, _key) == NULL) {
1007 if (_value == nil) {
1009 [self debugWithFormat:
1010 @"storing <nil> value in component variable %@", _key];
1013 if ([self->wocVariables objectForKey:_key])
1014 [self setObject:nil forKey:_key];
1019 if ([self logComponentVariableCreations]) {
1020 /* only if we have a subclass, we can store values in ivars ... */
1021 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1022 [self debugWithFormat:
1023 @"Created component variable (class=%@): '%@'.",
1024 NSStringFromClass(self->isa), _key];
1029 [self setObject:_value forKey:_key];
1033 [self debugWithFormat:
1034 @"value %@ could not set via method or KVC "
1035 @"(self responds to %@: %s).",
1037 [self respondsToSelector:NSSelectorFromString(_key)] ? "yes" : "no"];
1042 - (id)valueForKey:(NSString *)_key {
1045 if ((value = WOGetKVCValueUsingMethod(self, _key)))
1049 [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1051 if ((value = [self objectForKey:_key]))
1057 #else /* use fallback methods on other Foundation libraries */
1059 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
1060 // Note: this is not used on libFoundation, insufficient KVC implementation
1062 if (_value == nil) {
1064 [self debugWithFormat:
1065 @"storing <nil> value in component variable %@", _key];
1068 if ([self->wocVariables objectForKey:_key])
1069 [self setObject:nil forKey:_key];
1075 if ([self logComponentVariableCreations]) {
1076 /* only if we have a subclass, we can store values in ivars ... */
1077 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1078 [self debugWithFormat:@"Created component variable (class=%@): '%@'.",
1079 NSStringFromClass(self->isa), _key];
1084 [self setObject:_value forKey:_key];
1086 - (id)valueForUndefinedKey:(NSString *)_key {
1087 // Note: this is not used on libFoundation, insufficient KVC implementation
1089 [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1091 return [self objectForKey:_key];
1094 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
1095 // deprecated: pre-Panther method
1096 [self setValue:_value forUndefinedKey:_key];
1098 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1099 // deprecated: pre-Panther method
1100 return [self valueForUndefinedKey:_key];
1103 - (void)unableToSetNilForKey:(NSString *)_key {
1104 // TODO: should we call setValue:NSNull forKey?
1105 [self errorWithFormat:@"unable to set 'nil' for key: '%@'", _key];
1110 - (void)validationFailedWithException:(NSException *)_exception
1111 value:(id)_value keyPath:(NSString *)_keyPath
1113 [self warnWithFormat:
1114 @"formatter failed for value %@ (keyPath=%@): %@",
1115 _value, _keyPath, [_exception reason]];
1120 - (BOOL)isEventLoggingEnabled {
1124 - (BOOL)isDebuggingEnabled {
1127 - (NSString *)loggingPrefix {
1131 if ([n length] == 0)
1132 return @"<component without name>";
1137 /* woo/plist unarchiving */
1139 - (id)unarchiver:(EOKeyValueUnarchiver *)_archiver
1140 objectForReference:(id)_keyPath
1142 return [self valueForKeyPath:_keyPath];
1147 - (id)copyWithZone:(NSZone *)_zone {
1148 // TODO: find out who triggers this
1149 return [self retain];
1154 - (NSString *)description {
1155 NSMutableString *str;
1158 str = [NSMutableString stringWithCapacity:128];
1159 [str appendFormat:@"<0x%08X[%@]: name=%@", self,
1160 NSStringFromClass([self class]), [self name]];
1162 if (self->parentComponent)
1163 [str appendFormat:@" parent=%@", [self->parentComponent name]];
1164 if (self->subcomponents)
1165 [str appendFormat:@" #subs=%i", [self->subcomponents count]];
1167 if (self->componentFlags.isAwake)
1168 [str appendFormat:@" awake=0x%08X", self->context];
1169 else if (self->context == nil)
1170 [str appendString:@" no-ctx"];
1172 if ((tmp = _getExtraVar(self, @"__worm")))
1173 [str appendFormat:@" rm=%@", tmp];
1175 [str appendString:@">"];
1179 @end /* WOComponent */
1181 @implementation WOComponent(Statistics)
1183 - (NSString *)descriptionForResponse:(WOResponse *)_response
1184 inContext:(WOContext *)_context
1189 @end /* WOComponent(Statistics) */
1191 @implementation WOComponent(AdvancedBindingAccessors)
1193 - (void)setUnsignedIntValue:(unsigned)_value forBinding:(NSString *)_name {
1194 [self setValue:[NSNumber numberWithUnsignedInt:_value] forBinding:_name];
1196 - (unsigned)unsignedIntValueForBinding:(NSString *)_name {
1197 return [[self valueForBinding:_name] unsignedIntValue];
1200 - (void)setIntValue:(int)_value forBinding:(NSString *)_name {
1201 [self setValue:[NSNumber numberWithInt:_value] forBinding:_name];
1203 - (int)intValueForBinding:(NSString *)_name {
1204 return [[self valueForBinding:_name] intValue];
1207 - (void)setBoolValue:(BOOL)_value forBinding:(NSString *)_name {
1208 [self setValue:[NSNumber numberWithBool:_value] forBinding:_name];
1210 - (BOOL)boolValueForBinding:(NSString *)_name {
1211 return [[self valueForBinding:_name] boolValue];
1214 #if !NG_USE_KVC_FALLBACK
1215 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1216 [self logWithFormat:@"query for unbound key: %@", _key];
1217 return [super handleQueryWithUnboundKey:_key];
1221 @end /* WOComponent(AdvancedBindingAccessors) */