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;
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 hasSession]
348 ? [[self session] languages]
349 : [[[self context] request] browserLanguages];
352 return [[self resourceManager]
353 pathToComponentNamed:[self name]
354 inFramework:[self frameworkName]
355 languages:languages];
357 - (void)setBaseURL:(NSURL *)_url {
358 ASSIGNCOPY(self->wocBaseURL, _url);
363 if (self->wocBaseURL)
364 return self->wocBaseURL;
366 url = [(WOApplication *)[self application] baseURL];
368 [[NSURL URLWithString:@"WebServerResources" relativeToURL:url] copy];
369 return self->wocBaseURL;
372 - (NSString *)componentActionURLForContext:(WOContext *)_ctx {
373 return [@"/" stringByAppendingString:[self name]];
376 - (WOApplication *)application {
377 if (self->application == nil)
378 return (self->application = [WOApplication application]);
379 return self->application;
382 - (id)existingSession {
384 return self->session;
386 if ([[self context] hasSession])
387 return [self session];
391 - (WOSession *)session {
392 if (self->session == nil) {
393 if ((self->session = [[self context] session]) == nil) {
394 [self debugWithFormat:@"could not get session object from context %@",
399 if (self->session == nil)
400 [self warnWithFormat:@"missing session for component!"];
402 return self->session;
405 - (void)_setContext:(WOContext *)_ctx {
406 self->context = _ctx;
408 - (WOContext *)context {
410 return self->context;
412 [self warnWithFormat:
413 @"missing context in component 0x%08X (component%s)",
415 self->componentFlags.isAwake ? " is awake" : " is not awake"];
416 if (abortOnMissingCtx) {
417 [self errorWithFormat:@"aborting, because ctx is missing !"];
421 if (self->application == nil)
422 self->application = [WOApplication application];
423 [self _setContext:[self->application context]];
425 if (self->context == nil)
426 [self warnWithFormat:@"could not determine context object!"];
428 return self->context;
432 return [[self context] hasSession];
435 - (void)setCachingEnabled:(BOOL)_flag {
436 self->componentFlags.reloadTemplates = _flag ? NO : YES;
438 - (BOOL)isCachingEnabled {
439 return (self->componentFlags.reloadTemplates == NO) ? YES : NO;
442 - (WOComponent *)pageWithName:(NSString *)_name {
443 NSArray *languages = nil;
444 WOResourceManager *rm;
445 WOComponent *component;
447 languages = [self hasSession]
448 ? [(WOSession *)[self session] languages]
449 : [[[self context] request] browserLanguages];
451 rm = [self resourceManager];
454 Note: this API is somewhat broken since the component expects the
455 -initWithContext: message for initialization yet we pass no
458 component = [rm pageWithName:_name languages:languages];
460 // Note: should we call ensureAwakeInContext or is this to early ?
461 // probably the component should be woken up if it enters the ctx.
462 // If we create a page but never render it, we may get a warning
463 // that a context will dealloc but the page is active (yet not awake)
464 // Note: awake is not the same like "has context"! A component can have a
465 // context without being awake - maybe we need an additional method
466 // to hook up a component but the awake list
467 if (wakeupPageOnCreation)
468 [component ensureAwakeInContext:[self context]];
472 - (NSString *)stringForKey:(NSString *)_key
473 inTableNamed:(NSString *)_tableName
474 withDefaultValue:(NSString *)_default
479 langs = [self hasSession]
480 ? [(WOSession *)[self session] languages]
481 : [[[self context] request] browserLanguages];
483 return [[[self application]
486 inTableNamed:_tableName
487 withDefaultValue:_default
491 - (void)setName:(NSString *)_name {
492 if (![_name isNotNull])
493 [self warnWithFormat:@"setting 'nil' name on component!"];
495 ASSIGNCOPY(self->wocName, _name);
498 - (void)setBindings:(NSDictionary *)_bindings {
499 // this is _very_ private and used by WOComponentReference
500 ASSIGNCOPY(self->wocBindings, _bindings);
502 - (NSDictionary *)_bindings {
504 return self->wocBindings;
507 - (void)setSubComponents:(NSDictionary *)_dictionary {
508 ASSIGNCOPY(self->subcomponents, _dictionary);
510 - (NSDictionary *)_subComponents {
512 return self->subcomponents;
515 - (void)setParent:(WOComponent *)_parent {
516 self->parentComponent = _parent;
518 - (WOComponent *)parent {
519 return self->parentComponent;
522 /* language change */
524 - (void)languageArrayDidChange {
529 - (NSString *)elementID {
535 - (id<WOActionResults>)redirectToLocation:(id)_loc {
536 WOContext *ctx = [self context];
543 if ((r = [ctx response]) == nil)
544 r = [[[WOResponse alloc] init] autorelease];
546 if ([_loc isKindOfClass:[NSURL class]])
547 target = [_loc absoluteString];
549 _loc = [_loc stringValue];
550 if ([_loc isAbsoluteURL])
552 else if ([_loc isAbsolutePath])
555 target = [[ctx request] uri];
557 // TODO: check whether the algorithm is correct
558 if (![target hasSuffix:@"/"])
559 target = [target stringByDeletingLastPathComponent];
560 target = [target stringByAppendingPathComponent:_loc];
566 [r setStatus:302 /* moved */];
567 [r setHeader:target forKey:@"location"];
571 - (void)setResourceManager:(WOResourceManager *)_rm {
572 _setExtraVar(self, @"__worm", _rm);
574 - (WOResourceManager *)resourceManager {
575 WOResourceManager *rm;
578 if ((rm = _getExtraVar(self, @"__worm")))
581 /* ask parent component ... */
582 if ((p = [self parent])) {
583 NSAssert2(p != self, @"parent component == component !!! (%@ vs %@)",
585 if ((rm = [p resourceManager]))
589 /* ask application ... */
590 return [[self application] resourceManager];
593 - (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_ext {
594 NSFileManager *fm = [NSFileManager defaultManager];
595 NSEnumerator *languages = nil;
596 NSString *language = nil;
597 BOOL isDirectory = NO;
598 NSString *cpath = [self path];
600 if (_ext) _name = [_name stringByAppendingPathExtension:_ext];
603 [self warnWithFormat:@"no path set in component %@", [self name]];
606 if (![fm fileExistsAtPath:cpath isDirectory:&isDirectory]) {
607 [self warnWithFormat:@"component directory %@ does not exist !", cpath];
611 [self warnWithFormat:@"component path %@ is not a directory !", cpath];
615 // check in language projects
617 languages = [[(WOSession *)[self session] languages] objectEnumerator];
618 while ((language = [languages nextObject])) {
619 language = [[cpath stringByAppendingPathComponent:
620 [language stringByAppendingPathExtension:@"lproj"]]
621 stringByAppendingPathExtension:_name];
623 if ([fm fileExistsAtPath:language])
627 // check in component
628 cpath = [cpath stringByAppendingPathComponent:_name];
629 if ([fm fileExistsAtPath:cpath])
637 - (WOElement *)templateWithHTMLString:(NSString *)_html
638 declarationString:(NSString *)_wod
639 languages:(NSArray *)_languages
641 return [self notImplemented:_cmd];
643 - (WOElement *)templateWithHTMLString:(NSString *)_html
644 declarationString:(NSString *)_wod
647 return [self templateWithHTMLString:_html
648 declarationString:_wod
649 languages:[(WOSession *)[self session] languages]];
652 - (WOElement *)templateWithName:(NSString *)_name {
653 WOResourceManager *resourceManager;
657 if ((resourceManager = [self resourceManager]) == nil) {
658 [self errorWithFormat:@"%s: could not determine resource manager !",
659 __PRETTY_FUNCTION__];
663 languages = [self hasSession]
664 ? [(WOSession *)[self session] languages]
665 : [[[self context] request] browserLanguages];
667 tmpl = [resourceManager templateWithName:_name languages:languages];
668 if (debugTemplates) [self debugWithFormat:@"found template: %@", tmpl];
672 - (void)setTemplate:(id)_template {
674 WO has private API for this:
675 - (void)setTemplate:(WOElement *)template;
676 As mentioned in the OmniGroup WO mailing list ...
678 _setExtraVar(self, @"__wotemplate", _template);
680 - (WOElement *)_woComponentTemplate {
683 if ((element = _getExtraVar(self, @"__wotemplate")))
686 return [self templateWithName:[self name]];
689 /* child components */
691 - (WOComponent *)childComponentWithName:(NSString *)_name {
694 child = [self->subcomponents objectForKey:_name];
695 if ([child isComponentFault]) {
696 NSMutableDictionary *tmp;
698 child = [child resolveWithParent:self];
700 [self warnWithFormat:@"Could not resolve component fault: %@", _name];
704 tmp = [self->subcomponents mutableCopy];
705 [tmp setObject:child forKey:_name];
706 [self->subcomponents release]; self->subcomponents = nil;
707 self->subcomponents = [tmp copy];
708 [tmp release]; tmp = nil;
713 - (BOOL)synchronizesVariablesWithBindings {
717 - (void)setValue:(id)_value forBinding:(NSString *)_name {
720 WODynamicElement *content;
722 ctx = [self context];
723 parent = [ctx parentComponent];
724 content = [ctx componentContent];
727 parent = [self parent];
728 [self warnWithFormat:@"tried to set value of binding '%@' in component "
729 @"'%@' without parent component (parent is '%@') !",
730 _name, [self name], [parent name]];
733 [[self retain] autorelease];
734 [[content retain] autorelease];
736 WOContext_leaveComponent(ctx, self);
737 [[self->wocBindings objectForKey:_name] setValue:_value inComponent:parent];
738 WOContext_enterComponent(ctx, self, content);
740 - (id)valueForBinding:(NSString *)_name {
743 WODynamicElement *content;
746 ctx = [self context];
747 parent = [ctx parentComponent];
748 content = [ctx componentContent];
751 parent = [self parent];
752 [self warnWithFormat:@"tried to retrieve value of binding '%@' in"
753 @" component '%@' without parent component (parent is '%@') !",
754 _name, [self name], [parent name]];
757 [[self retain] autorelease];
758 [[content retain] autorelease];
760 WOContext_leaveComponent(ctx, self);
761 value = [[self->wocBindings objectForKey:_name] valueInComponent:parent];
762 WOContext_enterComponent(ctx, self, content);
767 - (BOOL)hasBinding:(NSString *)_name {
768 return ([self->wocBindings objectForKey:_name] != nil) ? YES : NO;
771 - (BOOL)canSetValueForBinding:(NSString *)_name {
772 WOAssociation *binding;
774 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
777 return [binding isValueSettable];
779 - (BOOL)canGetValueForBinding:(NSString *)_name {
780 WOAssociation *binding;
782 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
788 - (id)performParentAction:(NSString *)_name {
791 WODynamicElement *content;
795 ctx = [self context];
796 parent = [ctx parentComponent];
797 content = [ctx componentContent];
798 action = NSSelectorFromString(_name);
800 if (parent == nil) return nil;
801 if (action == NULL) return nil;
803 NSAssert(parent != self, @"parent component equals current component");
805 if (![parent respondsToSelector:action]) {
806 [self debugWithFormat:@"parent %@ doesn't respond to %@",
807 [parent name], _name];
811 self = [self retain];
813 WOContext_leaveComponent(ctx, self);
814 *(&result) = [parent performSelector:action];
815 WOContext_enterComponent(ctx, self, content);
819 [localException raise];
829 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
833 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
834 WOElement *template = nil;
836 NSAssert1(self->componentFlags.isAwake,
837 @"component %@ is not awake !", self);
839 [self _setContext:_ctx];
840 template = [self _woComponentTemplate];
845 if (template->takeValues) {
846 template->takeValues(template,
847 @selector(takeValuesFromRequest:inContext:),
851 [template takeValuesFromRequest:_req inContext:_ctx];
854 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
855 WOElement *template = nil;
858 NSAssert1(self->componentFlags.isAwake, @"component %@ is not awake!", self);
860 [self _setContext:_ctx];
861 template = [self _woComponentTemplate];
862 result = [template invokeActionForRequest:_req inContext:_ctx];
866 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
867 WOElement *template = nil;
868 NSTimeInterval st = 0.0;
870 NSAssert1(self->componentFlags.isAwake,
871 @"component %@ is not awake !", self);
873 if (self->context != _ctx) {
874 [self debugWithFormat:@"%s: component ctx != ctx (%@ vs %@)",
875 __PRETTY_FUNCTION__, self->context, _ctx];
879 [self _setContext:_ctx];
881 if ((template = [self _woComponentTemplate]) == nil) {
883 [self debugWithFormat:@"component has no template (rm=%@).",
884 [self resourceManager]];
890 st = [[NSDateClass date] timeIntervalSince1970];
892 if (template->appendResponse) {
893 template->appendResponse(template,
894 @selector(appendToResponse:inContext:),
898 [template appendToResponse:_response inContext:_ctx];
903 diff = [[NSDateClass date] timeIntervalSince1970] - st;
905 for (i = [_ctx componentStackCount]; i >= 0; i--)
908 [perfLogger logWithFormat:@"Template %@ (comp %@): %0.3fs\n",
915 /* WOActionResults */
917 - (WOResponse *)generateResponse {
918 WOResponse *response = nil;
919 WOContext *ctx = nil;
922 ctx = [self context];
923 ctxID = [ctx contextID];
924 response = [WOResponse responseWithRequest:[ctx request]];
927 [self debugWithFormat:@"missing ctx-id for context %@", ctx];
931 [ctx deleteAllElementIDComponents];
932 [ctx appendElementIDComponent:ctxID];
934 WOContext_enterComponent(ctx, self, nil);
935 [self appendToResponse:response inContext:ctx];
936 WOContext_leaveComponent(ctx, self);
938 [ctx deleteLastElementIDComponent];
943 if ([[[ctx request] method] isEqualToString:@"HEAD"])
944 [response setContent:[NSData data]];
947 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
948 if ([[ctx application] isPageRefreshOnBacktrackEnabled])
949 [response disableClientCaching];
956 - (void)encodeWithCoder:(NSCoder *)_coder {
957 BOOL doesReloadTemplates = self->componentFlags.reloadTemplates;
959 [_coder encodeObject:self->wocBindings];
960 [_coder encodeObject:self->wocName];
961 [_coder encodeConditionalObject:self->parentComponent];
962 [_coder encodeObject:self->subcomponents];
963 [_coder encodeObject:self->wocVariables];
964 [_coder encodeConditionalObject:self->session];
965 [_coder encodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
967 - (id)initWithCoder:(NSCoder *)_decoder {
968 if ((self = [super init])) {
969 BOOL doesReloadTemplates = YES;
971 self->wocBindings = [[_decoder decodeObject] retain];
972 self->wocName = [[_decoder decodeObject] retain];
973 self->parentComponent = [_decoder decodeObject]; // non-retained
974 self->subcomponents = [[_decoder decodeObject] retain];
975 self->wocVariables = [[_decoder decodeObject] retain];
976 self->session = [_decoder decodeObject]; // non-retained
978 [_decoder decodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
979 [self setCachingEnabled:!doesReloadTemplates];
984 /* component variables */
986 - (BOOL)isStateless {
990 [self->wocVariables removeAllObjects];
993 - (void)setObject:(id)_obj forKey:(NSString *)_key {
994 _setExtraVar(self, _key, _obj);
996 - (id)objectForKey:(NSString *)_key {
997 return _getExtraVar(self, _key);
999 - (NSDictionary *)variableDictionary {
1000 return self->wocVariables;
1003 - (BOOL)logComponentVariableCreations {
1004 /* only if we have a subclass, we can store values in ivars ... */
1005 return (self->isa != WOComponentClass) ? YES : NO;
1008 #if !NG_USE_KVC_FALLBACK /* only override on libFoundation */
1010 - (void)takeValue:(id)_value forKey:(NSString *)_key {
1011 if (WOSetKVCValueUsingMethod(self, _key, _value)) {
1015 if (WOGetKVCGetMethod(self, _key) == NULL) {
1016 if (_value == nil) {
1018 [self debugWithFormat:
1019 @"storing <nil> value in component variable %@", _key];
1022 if ([self->wocVariables objectForKey:_key])
1023 [self setObject:nil forKey:_key];
1028 if ([self logComponentVariableCreations]) {
1029 /* only if we have a subclass, we can store values in ivars ... */
1030 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1031 [self debugWithFormat:
1032 @"Created component variable (class=%@): '%@'.",
1033 NSStringFromClass(self->isa), _key];
1038 [self setObject:_value forKey:_key];
1042 [self debugWithFormat:
1043 @"value %@ could not set via method or KVC "
1044 @"(self responds to %@: %s).",
1046 [self respondsToSelector:NSSelectorFromString(_key)] ? "yes" : "no"];
1051 - (id)valueForKey:(NSString *)_key {
1054 if ((value = WOGetKVCValueUsingMethod(self, _key)))
1058 [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1060 if ((value = [self objectForKey:_key]))
1066 #else /* use fallback methods on other Foundation libraries */
1068 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
1069 // Note: this is not used on libFoundation, insufficient KVC implementation
1071 if (_value == nil) {
1073 [self debugWithFormat:
1074 @"storing <nil> value in component variable %@", _key];
1077 if ([self->wocVariables objectForKey:_key])
1078 [self setObject:nil forKey:_key];
1084 if ([self logComponentVariableCreations]) {
1085 /* only if we have a subclass, we can store values in ivars ... */
1086 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1087 [self debugWithFormat:@"Created component variable (class=%@): '%@'.",
1088 NSStringFromClass(self->isa), _key];
1093 [self setObject:_value forKey:_key];
1095 - (id)valueForUndefinedKey:(NSString *)_key {
1096 // Note: this is not used on libFoundation, insufficient KVC implementation
1098 [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1100 return [self objectForKey:_key];
1103 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
1104 // deprecated: pre-Panther method
1105 [self setValue:_value forUndefinedKey:_key];
1107 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1108 // deprecated: pre-Panther method
1109 return [self valueForUndefinedKey:_key];
1112 - (void)unableToSetNilForKey:(NSString *)_key {
1113 // TODO: should we call setValue:NSNull forKey?
1114 [self errorWithFormat:@"unable to set 'nil' for key: '%@'", _key];
1119 - (void)validationFailedWithException:(NSException *)_exception
1120 value:(id)_value keyPath:(NSString *)_keyPath
1122 [self warnWithFormat:
1123 @"formatter failed for value %@ (keyPath=%@): %@",
1124 _value, _keyPath, [_exception reason]];
1129 - (BOOL)isEventLoggingEnabled {
1133 - (BOOL)isDebuggingEnabled {
1136 - (NSString *)loggingPrefix {
1140 if ([n length] == 0)
1141 return @"<component without name>";
1146 /* woo/plist unarchiving */
1148 - (id)unarchiver:(EOKeyValueUnarchiver *)_archiver
1149 objectForReference:(id)_keyPath
1151 return [self valueForKeyPath:_keyPath];
1156 - (id)copyWithZone:(NSZone *)_zone {
1157 // TODO: find out who triggers this
1158 return [self retain];
1163 - (NSString *)description {
1164 NSMutableString *str;
1167 str = [NSMutableString stringWithCapacity:128];
1168 [str appendFormat:@"<0x%08X[%@]: name=%@", self,
1169 NSStringFromClass([self class]), [self name]];
1171 if (self->parentComponent)
1172 [str appendFormat:@" parent=%@", [self->parentComponent name]];
1173 if (self->subcomponents)
1174 [str appendFormat:@" #subs=%i", [self->subcomponents count]];
1176 if (self->componentFlags.isAwake)
1177 [str appendFormat:@" awake=0x%08X", self->context];
1178 else if (self->context == nil)
1179 [str appendString:@" no-ctx"];
1181 if ((tmp = _getExtraVar(self, @"__worm")))
1182 [str appendFormat:@" rm=%@", tmp];
1184 [str appendString:@">"];
1188 @end /* WOComponent */
1190 @implementation WOComponent(Statistics)
1192 - (NSString *)descriptionForResponse:(WOResponse *)_response
1193 inContext:(WOContext *)_context
1198 @end /* WOComponent(Statistics) */
1200 @implementation WOComponent(AdvancedBindingAccessors)
1202 - (void)setUnsignedIntValue:(unsigned)_value forBinding:(NSString *)_name {
1203 [self setValue:[NSNumber numberWithUnsignedInt:_value] forBinding:_name];
1205 - (unsigned)unsignedIntValueForBinding:(NSString *)_name {
1206 return [[self valueForBinding:_name] unsignedIntValue];
1209 - (void)setIntValue:(int)_value forBinding:(NSString *)_name {
1210 [self setValue:[NSNumber numberWithInt:_value] forBinding:_name];
1212 - (int)intValueForBinding:(NSString *)_name {
1213 return [[self valueForBinding:_name] intValue];
1216 - (void)setBoolValue:(BOOL)_value forBinding:(NSString *)_name {
1217 [self setValue:[NSNumber numberWithBool:_value] forBinding:_name];
1219 - (BOOL)boolValueForBinding:(NSString *)_name {
1220 return [[self valueForBinding:_name] boolValue];
1223 #if !NG_USE_KVC_FALLBACK
1224 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1225 [self logWithFormat:@"query for unbound key: %@", _key];
1226 return [super handleQueryWithUnboundKey:_key];
1230 @end /* WOComponent(AdvancedBindingAccessors) */