2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
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;
56 static BOOL profElements = NO;
57 static BOOL debugOn = NO;
58 static BOOL debugComponentAwake = NO;
59 static BOOL debugTemplates = NO;
60 static BOOL abortOnAwakeComponentInCtxDealloc = NO;
61 static BOOL abortOnMissingCtx = NO;
62 static BOOL wakeupPageOnCreation = NO;
65 // TODO: is really v4 for baseURL/cycleContext ivar changes
66 return [super version] + 0 /* v2 */;
69 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
70 static BOOL didInit = NO;
74 NSAssert2([super version] == 2,
75 @"invalid superclass (%@) version %i !",
76 NSStringFromClass([self superclass]), [super version]);
78 WOComponentClass = [WOComponent class];
79 NSDateClass = [NSDate class];
81 profElements = [ud boolForKey:@"WOProfileElements"];
82 debugOn = [WOApplication isDebuggingEnabled];
83 debugComponentAwake = [ud boolForKey:@"WODebugComponentAwake"];
84 abortOnAwakeComponentInCtxDealloc =
85 [ud boolForKey:@"WOCoreOnAwakeComponentInCtxDealloc"];
89 if ((self = [super init])) {
90 NSNotificationCenter *nc;
91 WOComponentDefinition *cdef;
93 if ((cdef = (id)self->wocVariables)) {
94 // HACK CD, see WOComponentDefinition
95 self->wocVariables = nil;
98 if (self->wocName == nil)
99 self->wocName = [NSStringFromClass([self class]) copy];
101 [self setCachingEnabled:[[self application] isCachingEnabled]];
103 /* finish initialization */
106 [cdef _finishInitializingComponent:self];
107 [cdef release]; cdef = nil;
109 #if !APPLE_FOUNDATION_LIBRARY && !NeXT_Foundation_LIBRARY
111 /* this is triggered by Publisher on MacOSX */
112 [self debugWithFormat:
113 @"Note: got no component definition according to HACK CD"];
117 /* add to notification center */
119 nc = [NSNotificationCenter defaultCenter];
121 [nc addObserver:self selector:@selector(_sessionWillDealloc:)
122 name:@"WOSessionWillDeallocate" object:nil];
124 [nc addObserver:self selector:@selector(_contextWillDealloc:)
125 name:@"WOContextWillDeallocate" object:nil];
129 - (id)initWithContext:(WOContext *)_ctx {
130 [self _setContext:_ctx];
131 if ((self = [self init])) {
133 [self ensureAwakeInContext:self->context];
136 @"WARNING: no context given to -initWithContext: ..."];
143 [[NSNotificationCenter defaultCenter] removeObserver:self];
145 [[self->subcomponents allValues]
146 makeObjectsPerformSelector:@selector(setParent:)
148 [self->subcomponents release];
150 [self->wocClientObject release];
151 [self->wocBindings release];
152 [self->wocVariables release];
153 [self->wocName release];
154 [self->wocBaseURL release];
158 static inline void _setExtraVar(WOComponent *self, NSString *_key, id _obj) {
160 if (self->wocVariables == nil)
161 self->wocVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
163 [self->wocVariables setObject:_obj forKey:_key];
166 [self->wocVariables removeObjectForKey:_key];
168 static inline id _getExtraVar(WOComponent *self, NSString *_key) {
169 return [self->wocVariables objectForKey:_key];
174 - (void)_sessionWillDealloc:(NSNotification *)_notification {
176 NSAssert(_notification, @"missing valid session arg ...");
179 if (self->session == nil) {
180 /* component isn't interested in session anymore anyway ... */
183 if (self->session != [_notification object])
184 /* not the component's context ... */
188 [self debugWithFormat:@"resetting sn/ctx because session will dealloc .."];
191 if (self->componentFlags.isAwake) {
193 @"WARNING: session will dealloc, "
194 @"but component 0x%08X is awake (ctx=%@) !", self, self->context];
195 [self _sleepWithContext:self->context];
199 [self _setContext:nil];
201 - (void)_contextWillDealloc:(NSNotification *)_notification {
203 NSAssert(_notification, @"missing valid notification arg ...");
206 if (self->context == nil)
207 /* component isn't interested in context anyway ... */
209 if (![[self->context contextID] isEqualToString:[_notification object]])
210 /* not the component's context ... */
214 [self debugWithFormat:@"resetting sn/ctx because context will dealloc .."];
217 if (self->componentFlags.isAwake) {
219 Note: this is not necessarily a problem, no specific reason to log
222 [self debugWithFormat:
223 @"WARNING: context %@ will dealloc, "
224 @"but component is awake in ctx %@!",
225 [_notification object], [self->context contextID]];
226 if (abortOnAwakeComponentInCtxDealloc)
229 [self _sleepWithContext:nil];
232 [self _setContext:nil];
242 if (self->componentFlags.isAwake) {
243 [self debugWithFormat:
244 @"WARNING: component should not be awake if sleep is called !"];
246 if (self->context == nil) {
247 [self debugWithFormat:
248 @"WARNING: context should not be nil if sleep is called !"];
252 self->componentFlags.isAwake = 0;
253 [self _setContext:nil];
254 self->application = nil;
258 - (void)ensureAwakeInContext:(WOContext *)_ctx {
260 NSAssert1(_ctx, @"missing context for awake (component=%@) ...", self);
263 if (debugComponentAwake)
264 [self logWithFormat:@"0x%08X ensureAwakeInContext:0x%08X", self, _ctx];
266 if (self->context == nil) [self _setContext:_ctx];
267 if (self->application == nil) self->application = [_ctx application];
269 if ((self->session == nil) && [_ctx hasSession])
270 self->session = [_ctx session];
272 self->componentFlags.isAwake = 1;
273 [_ctx _addAwakeComponent:self]; /* ensure that sleep is called */
275 /* awake subcomponents */
277 NSEnumerator *children;
280 children = [self->subcomponents objectEnumerator];
281 while ((child = [children nextObject]))
282 [child _awakeWithContext:_ctx];
288 - (void)_awakeWithContext:(WOContext *)_ctx {
289 if (self->componentFlags.isAwake)
292 [self ensureAwakeInContext:_ctx];
294 - (void)_sleepWithContext:(WOContext *)_ctx {
295 if (debugComponentAwake)
296 [self logWithFormat:@"0x%08X _sleepWithContext:0x%08X", self, _ctx];
298 if (_ctx != self->context) {
299 if ((self->context != nil) && (_ctx != nil)) {
300 /* component is active in different context ... */
301 [self debugWithFormat:
302 @"WARNING: sleep context mismatch (own=0x%08X vs given=0x%08X)",
303 self->context, _ctx];
308 if (self->componentFlags.isAwake) {
310 Sleep all child components, this is necessary to ensure some ordering
311 in the sleep calls. All awake components are put to sleep in any case
312 by the WOContext destructor.
314 NSEnumerator *children;
317 children = [self->subcomponents objectEnumerator];
318 self->componentFlags.isAwake = 0;
320 while ((child = [children nextObject]))
321 [child _sleepWithContext:_ctx];
325 [self _setContext:nil];
326 self->application = nil;
333 return self->wocName;
335 - (NSString *)frameworkName {
336 return [[NGBundle bundleForClass:[self class]] bundleName];
339 NSArray *languages = nil;
341 #if 0 // the component might not yet be awake !
342 languages = [self hasSession]
343 ? [[self session] languages]
344 : [[[self context] request] browserLanguages];
347 return [[self resourceManager]
348 pathToComponentNamed:[self name]
349 inFramework:[self frameworkName]
350 languages:languages];
352 - (void)setBaseURL:(NSURL *)_url {
353 ASSIGNCOPY(self->wocBaseURL, _url);
358 if (self->wocBaseURL)
359 return self->wocBaseURL;
361 url = [(WOApplication *)[self application] baseURL];
363 [[NSURL URLWithString:@"WebServerResources" relativeToURL:url] copy];
364 return self->wocBaseURL;
367 - (NSString *)componentActionURLForContext:(WOContext *)_ctx {
368 return [@"/" stringByAppendingString:[self name]];
371 - (WOApplication *)application {
372 if (self->application == nil)
373 return (self->application = [WOApplication application]);
374 return self->application;
377 - (id)existingSession {
379 return self->session;
381 if ([[self context] hasSession])
382 return [self session];
386 - (WOSession *)session {
387 if (self->session == nil) {
388 if ((self->session = [[self context] session]) == nil) {
389 [self logWithFormat:@"could not get session object from context %@",
394 if (self->session == nil)
395 [self logWithFormat:@"WARNING: missing session for component!"];
397 return self->session;
400 - (void)_setContext:(WOContext *)_ctx {
401 self->context = _ctx;
403 - (WOContext *)context {
405 return self->context;
407 [self debugWithFormat:
408 @"WARNING: missing context in component 0x%08X (component%s)",
410 self->componentFlags.isAwake ? " is awake" : " is not awake"];
411 if (abortOnMissingCtx) {
412 [self logWithFormat:@"aborting, because ctx is missing !"];
416 if (self->application == nil)
417 self->application = [WOApplication application];
418 [self _setContext:[self->application context]];
420 if (self->context == nil)
421 [self logWithFormat:@"WARNING: could not determine context object!"];
423 return self->context;
427 return [[self context] hasSession];
430 - (void)setCachingEnabled:(BOOL)_flag {
431 self->componentFlags.reloadTemplates = _flag ? NO : YES;
433 - (BOOL)isCachingEnabled {
434 return (self->componentFlags.reloadTemplates == NO) ? YES : NO;
437 - (WOComponent *)pageWithName:(NSString *)_name {
438 NSArray *languages = nil;
439 WOResourceManager *rm;
440 WOComponent *component;
442 languages = [self hasSession]
443 ? [(WOSession *)[self session] languages]
444 : [[[self context] request] browserLanguages];
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 hasSession]
475 ? [(WOSession *)[self session] languages]
476 : [[[self context] request] browserLanguages];
478 return [[[self application]
481 inTableNamed:_tableName
482 withDefaultValue:_default
486 - (void)setName:(NSString *)_name {
487 if (![_name isNotNull])
488 [self logWithFormat:@"WARNING: setting 'nil' name on component!"];
490 ASSIGNCOPY(self->wocName, _name);
493 - (void)setBindings:(NSDictionary *)_bindings {
494 // this is _very_ private and used by WOComponentReference
495 ASSIGNCOPY(self->wocBindings, _bindings);
497 - (NSDictionary *)_bindings {
499 return self->wocBindings;
502 - (void)setSubComponents:(NSDictionary *)_dictionary {
503 ASSIGNCOPY(self->subcomponents, _dictionary);
505 - (NSDictionary *)_subComponents {
507 return self->subcomponents;
510 - (void)setParent:(WOComponent *)_parent {
511 self->parentComponent = _parent;
513 - (WOComponent *)parent {
514 return self->parentComponent;
517 /* language change */
519 - (void)languageArrayDidChange {
524 - (NSString *)elementID {
530 - (id<WOActionResults>)redirectToLocation:(id)_loc {
531 WOContext *ctx = [self context];
538 if ((r = [ctx response]) == nil)
539 r = [[[WOResponse alloc] init] autorelease];
541 if ([_loc isKindOfClass:[NSURL class]])
542 target = [_loc absoluteString];
544 _loc = [_loc stringValue];
545 if ([_loc isAbsoluteURL])
547 else if ([_loc isAbsolutePath])
550 target = [[ctx request] uri];
552 // TODO: check whether the algorithm is correct
553 if (![target hasSuffix:@"/"])
554 target = [target stringByDeletingLastPathComponent];
555 target = [target stringByAppendingPathComponent:_loc];
561 [r setStatus:302 /* moved */];
562 [r setHeader:target forKey:@"location"];
566 - (void)setResourceManager:(WOResourceManager *)_rm {
567 _setExtraVar(self, @"__worm", _rm);
569 - (WOResourceManager *)resourceManager {
570 WOResourceManager *rm;
573 if ((rm = _getExtraVar(self, @"__worm")))
576 /* ask parent component ... */
577 if ((p = [self parent])) {
578 NSAssert2(p != self, @"parent component == component !!! (%@ vs %@)",
580 if ((rm = [p resourceManager]))
584 /* ask application ... */
585 return [[self application] resourceManager];
588 - (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_ext {
589 NSFileManager *fm = [NSFileManager defaultManager];
590 NSEnumerator *languages = nil;
591 NSString *language = nil;
592 BOOL isDirectory = NO;
593 NSString *cpath = [self path];
595 if (_ext) _name = [_name stringByAppendingPathExtension:_ext];
598 NSLog(@"WARNING: no path set in component %@", [self name]);
601 if (![fm fileExistsAtPath:cpath isDirectory:&isDirectory]) {
602 NSLog(@"WARNING: component directory %@ does not exist !", cpath);
606 NSLog(@"WARNING: component path %@ is not a directory !", cpath);
610 // check in language projects
612 languages = [[(WOSession *)[self session] languages] objectEnumerator];
613 while ((language = [languages nextObject])) {
614 language = [[cpath stringByAppendingPathComponent:
615 [language stringByAppendingPathExtension:@"lproj"]]
616 stringByAppendingPathExtension:_name];
618 if ([fm fileExistsAtPath:language])
622 // check in component
623 cpath = [cpath stringByAppendingPathComponent:_name];
624 if ([fm fileExistsAtPath:cpath])
632 - (WOElement *)templateWithHTMLString:(NSString *)_html
633 declarationString:(NSString *)_wod
634 languages:(NSArray *)_languages
636 return [self notImplemented:_cmd];
638 - (WOElement *)templateWithHTMLString:(NSString *)_html
639 declarationString:(NSString *)_wod
642 return [self templateWithHTMLString:_html
643 declarationString:_wod
644 languages:[(WOSession *)[self session] languages]];
647 - (WOElement *)templateWithName:(NSString *)_name {
648 WOResourceManager *resourceManager;
652 if ((resourceManager = [self resourceManager]) == nil) {
653 [self logWithFormat:@"ERROR(%s): could not determine resource manager !",
654 __PRETTY_FUNCTION__];
658 languages = [self hasSession]
659 ? [(WOSession *)[self session] languages]
660 : [[[self context] request] browserLanguages];
662 tmpl = [resourceManager templateWithName:_name languages:languages];
663 if (debugTemplates) [self logWithFormat:@"found template: %@", tmpl];
667 - (void)setTemplate:(id)_template {
669 WO has private API for this:
670 - (void)setTemplate:(WOElement *)template;
671 As mentioned in the OmniGroup WO mailing list ...
673 _setExtraVar(self, @"__wotemplate", _template);
675 - (WOElement *)_woComponentTemplate {
678 if ((element = _getExtraVar(self, @"__wotemplate")))
681 return [self templateWithName:[self name]];
684 /* child components */
686 - (WOComponent *)childComponentWithName:(NSString *)_name {
689 child = [self->subcomponents objectForKey:_name];
690 if ([child isComponentFault]) {
691 NSMutableDictionary *tmp;
693 child = [child resolveWithParent:self];
695 [self logWithFormat:@"Could not resolve component fault: %@", _name];
699 tmp = [self->subcomponents mutableCopy];
700 [tmp setObject:child forKey:_name];
701 [self->subcomponents release]; self->subcomponents = nil;
702 self->subcomponents = [tmp copy];
703 [tmp release]; tmp = nil;
708 - (BOOL)synchronizesVariablesWithBindings {
712 - (void)setValue:(id)_value forBinding:(NSString *)_name {
715 WODynamicElement *content;
717 ctx = [self context];
718 parent = [ctx parentComponent];
719 content = [ctx componentContent];
722 parent = [self parent];
723 NSLog(@"WARNING: tried to set value of binding '%@' in component '%@' "
724 @"without parent component (parent is '%@') !",
725 _name, [self name], [parent name]);
728 [[self retain] autorelease];
729 [[content retain] autorelease];
731 WOContext_leaveComponent(ctx, self);
732 [[self->wocBindings objectForKey:_name] setValue:_value inComponent:parent];
733 WOContext_enterComponent(ctx, self, content);
735 - (id)valueForBinding:(NSString *)_name {
738 WODynamicElement *content;
741 ctx = [self context];
742 parent = [ctx parentComponent];
743 content = [ctx componentContent];
746 parent = [self parent];
747 NSLog(@"WARNING: tried to retrieve value of binding '%@' in"
748 @" component '%@' without parent component (parent is '%@') !",
749 _name, [self name], [parent name]);
752 [[self retain] autorelease];
753 [[content retain] autorelease];
755 WOContext_leaveComponent(ctx, self);
756 value = [[self->wocBindings objectForKey:_name] valueInComponent:parent];
757 WOContext_enterComponent(ctx, self, content);
762 - (BOOL)hasBinding:(NSString *)_name {
763 return ([self->wocBindings objectForKey:_name] != nil) ? YES : NO;
766 - (BOOL)canSetValueForBinding:(NSString *)_name {
767 WOAssociation *binding;
769 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
772 return [binding isValueSettable];
774 - (BOOL)canGetValueForBinding:(NSString *)_name {
775 WOAssociation *binding;
777 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
783 - (id)performParentAction:(NSString *)_name {
786 WODynamicElement *content;
790 ctx = [self context];
791 parent = [ctx parentComponent];
792 content = [ctx componentContent];
793 action = NSSelectorFromString(_name);
795 if (parent == nil) return nil;
796 if (action == NULL) return nil;
798 NSAssert(parent != self, @"parent component equals current component");
800 if (![parent respondsToSelector:action]) {
801 [self logWithFormat:@"parent %@ doesn't respond to %@",
802 [parent name], _name];
806 self = [self retain];
808 WOContext_leaveComponent(ctx, self);
809 *(&result) = [parent performSelector:action];
810 WOContext_enterComponent(ctx, self, content);
814 [localException raise];
824 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
828 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
829 WOElement *template = nil;
831 NSAssert1(self->componentFlags.isAwake,
832 @"component %@ is not awake !", self);
834 [self _setContext:_ctx];
835 template = [self _woComponentTemplate];
840 if (template->takeValues) {
841 template->takeValues(template,
842 @selector(takeValuesFromRequest:inContext:),
846 [template takeValuesFromRequest:_req inContext:_ctx];
849 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
850 WOElement *template = nil;
853 NSAssert1(self->componentFlags.isAwake, @"component %@ is not awake!", self);
855 [self _setContext:_ctx];
856 template = [self _woComponentTemplate];
857 result = [template invokeActionForRequest:_req inContext:_ctx];
861 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
862 WOElement *template = nil;
863 NSTimeInterval st = 0.0;
865 NSAssert1(self->componentFlags.isAwake,
866 @"component %@ is not awake !", self);
868 if (self->context != _ctx) {
869 [self debugWithFormat:@"WARNING(%s): component ctx != ctx (%@ vs %@)",
870 __PRETTY_FUNCTION__, self->context, _ctx];
874 [self _setContext:_ctx];
876 if ((template = [self _woComponentTemplate]) == nil) {
878 [self debugWithFormat:@"component has no template (rm=%@).",
879 [self resourceManager]];
885 st = [[NSDateClass date] timeIntervalSince1970];
887 if (template->appendResponse) {
888 template->appendResponse(template,
889 @selector(appendToResponse:inContext:),
893 [template appendToResponse:_response inContext:_ctx];
898 diff = [[NSDateClass date] timeIntervalSince1970] - st;
900 for (i = [_ctx componentStackCount]; i >= 0; i--)
903 printf("Template %s (comp %s): %0.3fs\n",
904 [[_ctx elementID] cString],
905 [[self name] cString],
910 /* WOActionResults */
912 - (WOResponse *)generateResponse {
913 WOResponse *response = nil;
914 WOContext *ctx = nil;
917 ctx = [self context];
918 ctxID = [ctx contextID];
919 response = [WOResponse responseWithRequest:[ctx request]];
922 [self logWithFormat:@"missing ctx-id for context %@", ctx];
926 [ctx deleteAllElementIDComponents];
927 [ctx appendElementIDComponent:ctxID];
929 WOContext_enterComponent(ctx, self, nil);
930 [self appendToResponse:response inContext:ctx];
931 WOContext_leaveComponent(ctx, self);
933 [ctx deleteLastElementIDComponent];
938 if ([[[ctx request] method] isEqualToString:@"HEAD"])
939 [response setContent:[NSData data]];
942 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
943 if ([[ctx application] isPageRefreshOnBacktrackEnabled])
944 [response disableClientCaching];
951 - (void)encodeWithCoder:(NSCoder *)_coder {
952 BOOL doesReloadTemplates = self->componentFlags.reloadTemplates;
954 [_coder encodeObject:self->wocBindings];
955 [_coder encodeObject:self->wocName];
956 [_coder encodeConditionalObject:self->parentComponent];
957 [_coder encodeObject:self->subcomponents];
958 [_coder encodeObject:self->wocVariables];
959 [_coder encodeConditionalObject:self->session];
960 [_coder encodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
962 - (id)initWithCoder:(NSCoder *)_decoder {
963 if ((self = [super init])) {
964 BOOL doesReloadTemplates = YES;
966 self->wocBindings = [[_decoder decodeObject] retain];
967 self->wocName = [[_decoder decodeObject] retain];
968 self->parentComponent = [_decoder decodeObject]; // non-retained
969 self->subcomponents = [[_decoder decodeObject] retain];
970 self->wocVariables = [[_decoder decodeObject] retain];
971 self->session = [_decoder decodeObject]; // non-retained
973 [_decoder decodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
974 [self setCachingEnabled:!doesReloadTemplates];
979 /* component variables */
981 - (BOOL)isStateless {
985 [self->wocVariables removeAllObjects];
988 - (void)setObject:(id)_obj forKey:(NSString *)_key {
989 _setExtraVar(self, _key, _obj);
991 - (id)objectForKey:(NSString *)_key {
992 return _getExtraVar(self, _key);
994 - (NSDictionary *)variableDictionary {
995 return self->wocVariables;
998 - (BOOL)logComponentVariableCreations {
999 /* only if we have a subclass, we can store values in ivars ... */
1000 return (self->isa != WOComponentClass) ? YES : NO;
1003 #if !NG_USE_KVC_FALLBACK /* only override on libFoundation */
1005 - (void)takeValue:(id)_value forKey:(NSString *)_key {
1006 if (WOSetKVCValueUsingMethod(self, _key, _value)) {
1010 if (WOGetKVCGetMethod(self, _key) == NULL) {
1011 if (_value == nil) {
1013 [self debugWithFormat:
1014 @"storing <nil> value in component variable %@", _key];
1017 if ([self->wocVariables objectForKey:_key])
1018 [self setObject:nil forKey:_key];
1023 if ([self logComponentVariableCreations]) {
1024 /* only if we have a subclass, we can store values in ivars ... */
1025 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1026 [self logWithFormat:@"Created component variable (class=%@): '%@'.",
1027 NSStringFromClass(self->isa), _key];
1032 [self setObject:_value forKey:_key];
1036 [self logWithFormat:
1037 @"value %@ could not set via method or KVC "
1038 @"(self responds to %@: %s).",
1040 [self respondsToSelector:NSSelectorFromString(_key)] ? "yes" : "no"];
1045 - (id)valueForKey:(NSString *)_key {
1048 if ((value = WOGetKVCValueUsingMethod(self, _key)))
1052 [self logWithFormat:@"KVC: accessed the component variable %@", _key];
1054 if ((value = [self objectForKey:_key]))
1060 #else /* use fallback methods on other Foundation libraries */
1062 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
1063 // Note: this is not used on libFoundation, insufficient KVC implementation
1065 if (_value == nil) {
1067 [self debugWithFormat:
1068 @"storing <nil> value in component variable %@", _key];
1071 if ([self->wocVariables objectForKey:_key])
1072 [self setObject:nil forKey:_key];
1078 if ([self logComponentVariableCreations]) {
1079 /* only if we have a subclass, we can store values in ivars ... */
1080 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1081 [self logWithFormat:@"Created component variable (class=%@): '%@'.",
1082 NSStringFromClass(self->isa), _key];
1087 [self setObject:_value forKey:_key];
1089 - (id)valueForUndefinedKey:(NSString *)_key {
1090 // Note: this is not used on libFoundation, insufficient KVC implementation
1092 [self logWithFormat:@"KVC: accessed the component variable %@", _key];
1094 return [self objectForKey:_key];
1097 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
1098 // deprecated: pre-Panther method
1099 [self setValue:_value forUndefinedKey:_key];
1101 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1102 // deprecated: pre-Panther method
1103 return [self valueForUndefinedKey:_key];
1106 - (void)unableToSetNilForKey:(NSString *)_key {
1107 // TODO: should we call setValue:NSNull forKey?
1108 [self logWithFormat:@"ERROR: unable to set 'nil' for key: '%@'", _key];
1113 - (void)validationFailedWithException:(NSException *)_exception
1114 value:(id)_value keyPath:(NSString *)_keyPath
1116 [self logWithFormat:
1117 @"WARNING: formatter failed for value %@ (keyPath=%@): %@",
1118 _value, _keyPath, [_exception reason]];
1123 - (BOOL)isEventLoggingEnabled {
1127 - (BOOL)isDebuggingEnabled {
1130 - (NSString *)loggingPrefix {
1134 if ([n length] == 0)
1135 return @"<component without name>";
1140 /* woo/plist unarchiving */
1142 - (id)unarchiver:(EOKeyValueUnarchiver *)_archiver
1143 objectForReference:(id)_keyPath
1145 return [self valueForKeyPath:_keyPath];
1150 - (id)copyWithZone:(NSZone *)_zone {
1151 // TODO: find out who triggers this
1152 return [self retain];
1157 - (NSString *)description {
1158 NSMutableString *str;
1161 str = [NSMutableString stringWithCapacity:128];
1162 [str appendFormat:@"<0x%08X[%@]: name=%@", self,
1163 NSStringFromClass([self class]), [self name]];
1165 if (self->parentComponent)
1166 [str appendFormat:@" parent=%@", [self->parentComponent name]];
1167 if (self->subcomponents)
1168 [str appendFormat:@" #subs=%i", [self->subcomponents count]];
1170 if (self->componentFlags.isAwake)
1171 [str appendFormat:@" awake=0x%08X", self->context];
1172 else if (self->context == nil)
1173 [str appendString:@" no-ctx"];
1175 if ((tmp = _getExtraVar(self, @"__worm")))
1176 [str appendFormat:@" rm=%@", tmp];
1178 [str appendString:@">"];
1182 @end /* WOComponent */
1184 @implementation WOComponent(Statistics)
1186 - (NSString *)descriptionForResponse:(WOResponse *)_response
1187 inContext:(WOContext *)_context
1192 @end /* WOComponent(Statistics) */
1194 @implementation WOComponent(AdvancedBindingAccessors)
1196 - (void)setUnsignedIntValue:(unsigned)_value forBinding:(NSString *)_name {
1197 [self setValue:[NSNumber numberWithUnsignedInt:_value] forBinding:_name];
1199 - (unsigned)unsignedIntValueForBinding:(NSString *)_name {
1200 return [[self valueForBinding:_name] unsignedIntValue];
1203 - (void)setIntValue:(int)_value forBinding:(NSString *)_name {
1204 [self setValue:[NSNumber numberWithInt:_value] forBinding:_name];
1206 - (int)intValueForBinding:(NSString *)_name {
1207 return [[self valueForBinding:_name] intValue];
1210 - (void)setBoolValue:(BOOL)_value forBinding:(NSString *)_name {
1211 [self setValue:[NSNumber numberWithBool:_value] forBinding:_name];
1213 - (BOOL)boolValueForBinding:(NSString *)_name {
1214 return [[self valueForBinding:_name] boolValue];
1217 #if !NG_USE_KVC_FALLBACK
1218 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1219 [self logWithFormat:@"query for unbound key: %@", _key];
1220 return [super handleQueryWithUnboundKey:_key];
1224 @end /* WOComponent(AdvancedBindingAccessors) */