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])) {
138 if (self->context != nil)
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];
273 if (self->componentFlags.isAwake) {
274 if (self->context == _ctx) {
275 if (debugComponentAwake)
276 [self debugWithFormat:@"0x%08X already awake:0x%08X", self, _ctx];
283 if (self->context == nil) [self _setContext:_ctx];
284 if (self->application == nil) self->application = [_ctx application];
286 if ((self->session == nil) && [_ctx hasSession])
287 self->session = [_ctx session];
289 self->componentFlags.isAwake = 1;
290 [_ctx _addAwakeComponent:self]; /* ensure that sleep is called */
292 /* awake subcomponents */
294 NSEnumerator *children;
297 children = [self->subcomponents objectEnumerator];
298 while ((child = [children nextObject]) != nil)
299 [child _awakeWithContext:_ctx];
305 - (void)_awakeWithContext:(WOContext *)_ctx {
306 if (self->componentFlags.isAwake)
309 [self ensureAwakeInContext:_ctx];
311 - (void)_sleepWithContext:(WOContext *)_ctx {
312 if (debugComponentAwake)
313 [self debugWithFormat:@"0x%08X _sleepWithContext:0x%08X", self, _ctx];
315 if (_ctx != self->context) {
316 if ((self->context != nil) && (_ctx != nil)) {
317 /* component is active in different context ... */
318 [self warnWithFormat:
319 @"sleep context mismatch (own=0x%08X vs given=0x%08X)",
320 self->context, _ctx];
325 if (self->componentFlags.isAwake) {
327 Sleep all child components, this is necessary to ensure some ordering
328 in the sleep calls. All awake components are put to sleep in any case
329 by the WOContext destructor.
331 NSEnumerator *children;
334 children = [self->subcomponents objectEnumerator];
335 self->componentFlags.isAwake = 0;
337 while ((child = [children nextObject]))
338 [child _sleepWithContext:_ctx];
342 [self _setContext:nil];
343 self->application = nil;
350 return self->wocName;
352 - (NSString *)frameworkName {
353 return [[NGBundle bundleForClass:[self class]] bundleName];
356 NSArray *languages = nil;
358 #if 0 // the component might not yet be awake !
359 languages = [[self context] resourceLookupLanguages];
362 return [[self resourceManager]
363 pathToComponentNamed:[self name]
364 inFramework:[self frameworkName]
365 languages:languages];
367 - (void)setBaseURL:(NSURL *)_url {
368 ASSIGNCOPY(self->wocBaseURL, _url);
373 if (self->wocBaseURL)
374 return self->wocBaseURL;
376 url = [(WOApplication *)[self application] baseURL];
378 [[NSURL URLWithString:@"WebServerResources" relativeToURL:url] copy];
379 return self->wocBaseURL;
382 - (NSString *)componentActionURLForContext:(WOContext *)_ctx {
383 return [@"/" stringByAppendingString:[self name]];
386 - (WOApplication *)application {
387 if (self->application == nil)
388 return (self->application = [WOApplication application]);
389 return self->application;
392 - (id)existingSession {
394 return self->session;
396 if ([[self context] hasSession])
397 return [self session];
401 - (WOSession *)session {
402 if (self->session == nil) {
403 if ((self->session = [[self context] session]) == nil) {
404 [self debugWithFormat:@"could not get session object from context %@",
409 if (self->session == nil)
410 [self warnWithFormat:@"missing session for component!"];
412 return self->session;
415 - (void)_setContext:(WOContext *)_ctx {
416 self->context = _ctx;
418 - (WOContext *)context {
420 return self->context;
422 [self debugWithFormat:
423 @"missing context in component 0x%08X (component%s)",
425 self->componentFlags.isAwake ? " is awake" : " is not awake"];
426 if (abortOnMissingCtx) {
427 [self errorWithFormat:@"aborting, because ctx is missing !"];
431 if (self->application == nil)
432 self->application = [WOApplication application];
433 [self _setContext:[self->application context]];
435 if (self->context == nil)
436 [self warnWithFormat:@"could not determine context object!"];
438 return self->context;
442 return [[self context] hasSession];
445 - (void)setCachingEnabled:(BOOL)_flag {
446 self->componentFlags.reloadTemplates = _flag ? NO : YES;
448 - (BOOL)isCachingEnabled {
449 return (self->componentFlags.reloadTemplates == NO) ? YES : NO;
452 - (WOComponent *)pageWithName:(NSString *)_name {
454 WOResourceManager *rm;
455 WOComponent *component;
457 languages = [[self context] resourceLookupLanguages];
458 rm = [self resourceManager];
461 Note: this API is somewhat broken since the component expects the
462 -initWithContext: message for initialization yet we pass no
465 component = [rm pageWithName:_name languages:languages];
467 // Note: should we call ensureAwakeInContext or is this to early ?
468 // probably the component should be woken up if it enters the ctx.
469 // If we create a page but never render it, we may get a warning
470 // that a context will dealloc but the page is active (yet not awake)
471 // Note: awake is not the same like "has context"! A component can have a
472 // context without being awake - maybe we need an additional method
473 // to hook up a component but the awake list
474 if (wakeupPageOnCreation)
475 [component ensureAwakeInContext:[self context]];
479 - (NSString *)stringForKey:(NSString *)_key
480 inTableNamed:(NSString *)_tableName
481 withDefaultValue:(NSString *)_default
486 langs = [[self context] resourceLookupLanguages];
488 return [[[self application]
491 inTableNamed:_tableName
492 withDefaultValue:_default
496 - (void)setName:(NSString *)_name {
497 if (![_name isNotNull])
498 [self warnWithFormat:@"setting 'nil' name on component!"];
500 ASSIGNCOPY(self->wocName, _name);
503 - (void)setBindings:(NSDictionary *)_bindings {
504 // this is _very_ private and used by WOComponentReference
505 ASSIGNCOPY(self->wocBindings, _bindings);
507 - (NSDictionary *)_bindings {
509 return self->wocBindings;
512 - (void)setSubComponents:(NSDictionary *)_dictionary {
513 ASSIGNCOPY(self->subcomponents, _dictionary);
515 - (NSDictionary *)_subComponents {
517 return self->subcomponents;
520 - (void)setParent:(WOComponent *)_parent {
521 self->parentComponent = _parent;
523 - (WOComponent *)parent {
524 return self->parentComponent;
527 /* language change */
529 - (void)languageArrayDidChange {
534 - (NSString *)elementID {
540 - (id<WOActionResults>)redirectToLocation:(id)_loc {
541 WOContext *ctx = [self context];
548 if ((r = [ctx response]) == nil)
549 r = [[[WOResponse alloc] init] autorelease];
551 if ([_loc isKindOfClass:[NSURL class]])
552 target = [_loc absoluteString];
554 _loc = [_loc stringValue];
555 if ([_loc isAbsoluteURL])
557 else if ([_loc isAbsolutePath])
560 target = [[ctx request] uri];
562 // TODO: check whether the algorithm is correct
563 if (![target hasSuffix:@"/"])
564 target = [target stringByDeletingLastPathComponent];
565 target = [target stringByAppendingPathComponent:_loc];
571 [r setStatus:302 /* moved */];
572 [r setHeader:target forKey:@"location"];
576 - (void)setResourceManager:(WOResourceManager *)_rm {
577 _setExtraVar(self, @"__worm", _rm);
579 - (WOResourceManager *)resourceManager {
580 WOResourceManager *rm;
583 if ((rm = _getExtraVar(self, @"__worm")))
586 /* ask parent component ... */
587 if ((p = [self parent])) {
588 NSAssert2(p != self, @"parent component == component !!! (%@ vs %@)",
590 if ((rm = [p resourceManager]))
594 /* ask application ... */
595 return [[self application] resourceManager];
598 - (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_ext {
599 NSFileManager *fm = [NSFileManager defaultManager];
600 NSEnumerator *languages = nil;
601 NSString *language = nil;
602 BOOL isDirectory = NO;
603 NSString *cpath = [self path];
605 if (_ext) _name = [_name stringByAppendingPathExtension:_ext];
608 [self warnWithFormat:@"no path set in component %@", [self name]];
611 if (![fm fileExistsAtPath:cpath isDirectory:&isDirectory]) {
612 [self warnWithFormat:@"component directory %@ does not exist !", cpath];
616 [self warnWithFormat:@"component path %@ is not a directory !", cpath];
620 // check in language projects
622 languages = [[(WOSession *)[self session] languages] objectEnumerator];
623 while ((language = [languages nextObject])) {
624 language = [[cpath stringByAppendingPathComponent:
625 [language stringByAppendingPathExtension:@"lproj"]]
626 stringByAppendingPathExtension:_name];
628 if ([fm fileExistsAtPath:language])
632 // check in component
633 cpath = [cpath stringByAppendingPathComponent:_name];
634 if ([fm fileExistsAtPath:cpath])
642 - (WOElement *)templateWithHTMLString:(NSString *)_html
643 declarationString:(NSString *)_wod
644 languages:(NSArray *)_languages
646 return [self notImplemented:_cmd];
648 - (WOElement *)templateWithHTMLString:(NSString *)_html
649 declarationString:(NSString *)_wod
652 return [self templateWithHTMLString:_html
653 declarationString:_wod
654 languages:[(WOSession *)[self session] languages]];
657 - (WOElement *)templateWithName:(NSString *)_name {
658 WOResourceManager *resourceManager;
662 if ((resourceManager = [self resourceManager]) == nil) {
663 [self errorWithFormat:@"%s: could not determine resource manager !",
664 __PRETTY_FUNCTION__];
668 languages = [[self context] resourceLookupLanguages];
669 tmpl = [resourceManager templateWithName:_name languages:languages];
671 if (debugTemplates) [self debugWithFormat:@"found template: %@", tmpl];
675 - (void)setTemplate:(id)_template {
677 WO has private API for this:
678 - (void)setTemplate:(WOElement *)template;
679 As mentioned in the OmniGroup WO mailing list ...
681 _setExtraVar(self, @"__wotemplate", _template);
683 - (WOElement *)_woComponentTemplate {
686 if ((element = _getExtraVar(self, @"__wotemplate")))
689 return [self templateWithName:[self name]];
692 /* child components */
694 - (WOComponent *)childComponentWithName:(NSString *)_name {
697 child = [self->subcomponents objectForKey:_name];
698 if ([child isComponentFault]) {
699 NSMutableDictionary *tmp;
701 child = [child resolveWithParent:self];
703 [self warnWithFormat:@"Could not resolve component fault: %@", _name];
707 tmp = [self->subcomponents mutableCopy];
708 [tmp setObject:child forKey:_name];
709 [self->subcomponents release]; self->subcomponents = nil;
710 self->subcomponents = [tmp copy];
711 [tmp release]; tmp = nil;
716 - (BOOL)synchronizesVariablesWithBindings {
720 - (void)setValue:(id)_value forBinding:(NSString *)_name {
723 WODynamicElement *content;
725 ctx = [self context];
726 parent = [ctx parentComponent];
727 content = [ctx componentContent];
730 parent = [self parent];
731 [self warnWithFormat:@"tried to set value of binding '%@' in component "
732 @"'%@' without parent component (parent is '%@') !",
733 _name, [self name], [parent name]];
736 [[self retain] autorelease];
737 [[content retain] autorelease];
739 WOContext_leaveComponent(ctx, self);
740 [[self->wocBindings objectForKey:_name] setValue:_value inComponent:parent];
741 WOContext_enterComponent(ctx, self, content);
743 - (id)valueForBinding:(NSString *)_name {
746 WODynamicElement *content;
749 ctx = [self context];
750 parent = [ctx parentComponent];
751 content = [ctx componentContent];
754 parent = [self parent];
755 [self warnWithFormat:@"tried to retrieve value of binding '%@' in"
756 @" component '%@' without parent component (parent is '%@') !",
757 _name, [self name], [parent name]];
760 [[self retain] autorelease];
761 [[content retain] autorelease];
763 WOContext_leaveComponent(ctx, self);
764 value = [[self->wocBindings objectForKey:_name] valueInComponent:parent];
765 WOContext_enterComponent(ctx, self, content);
770 - (BOOL)hasBinding:(NSString *)_name {
771 return ([self->wocBindings objectForKey:_name] != nil) ? YES : NO;
774 - (BOOL)canSetValueForBinding:(NSString *)_name {
775 WOAssociation *binding;
777 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
780 return [binding isValueSettable];
782 - (BOOL)canGetValueForBinding:(NSString *)_name {
783 WOAssociation *binding;
785 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
791 - (id)performParentAction:(NSString *)_name {
794 WODynamicElement *content;
798 ctx = [self context];
799 parent = [ctx parentComponent];
800 content = [ctx componentContent];
801 action = NSSelectorFromString(_name);
803 if (parent == nil) return nil;
804 if (action == NULL) return nil;
806 NSAssert(parent != self, @"parent component equals current component");
808 if (![parent respondsToSelector:action]) {
809 [self debugWithFormat:@"parent %@ doesn't respond to %@",
810 [parent name], _name];
814 self = [self retain];
816 WOContext_leaveComponent(ctx, self);
817 *(&result) = [parent performSelector:action];
818 WOContext_enterComponent(ctx, self, content);
822 [localException raise];
832 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
836 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
837 WOElement *template = nil;
839 NSAssert1(self->componentFlags.isAwake,
840 @"component %@ is not awake !", self);
842 [self _setContext:_ctx];
843 template = [self _woComponentTemplate];
848 if (template->takeValues) {
849 template->takeValues(template,
850 @selector(takeValuesFromRequest:inContext:),
854 [template takeValuesFromRequest:_req inContext:_ctx];
857 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
858 WOElement *template = nil;
861 NSAssert1(self->componentFlags.isAwake, @"component %@ is not awake!", self);
863 [self _setContext:_ctx];
864 template = [self _woComponentTemplate];
865 result = [template invokeActionForRequest:_req inContext:_ctx];
869 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
870 WOElement *template = nil;
871 NSTimeInterval st = 0.0;
873 NSAssert1(self->componentFlags.isAwake,
874 @"component %@ is not awake !", self);
876 if (self->context != _ctx) {
877 [self debugWithFormat:@"%s: component ctx != ctx (%@ vs %@)",
878 __PRETTY_FUNCTION__, self->context, _ctx];
882 [self _setContext:_ctx];
884 if ((template = [self _woComponentTemplate]) == nil) {
886 [self debugWithFormat:@"component has no template (rm=%@).",
887 [self resourceManager]];
893 st = [[NSDateClass date] timeIntervalSince1970];
895 if (template->appendResponse) {
896 template->appendResponse(template,
897 @selector(appendToResponse:inContext:),
901 [template appendToResponse:_response inContext:_ctx];
906 diff = [[NSDateClass date] timeIntervalSince1970] - st;
908 for (i = [_ctx componentStackCount]; i >= 0; i--)
911 [perfLogger logWithFormat:@"Template %@ (comp %@): %0.3fs\n",
918 /* WOActionResults */
920 - (WOResponse *)generateResponse {
921 WOResponse *response = nil;
922 WOContext *ctx = nil;
925 ctx = [self context];
926 ctxID = [ctx contextID];
927 response = [WOResponse responseWithRequest:[ctx request]];
930 [self debugWithFormat:@"missing ctx-id for context %@", ctx];
934 [ctx deleteAllElementIDComponents];
935 [ctx appendElementIDComponent:ctxID];
937 WOContext_enterComponent(ctx, self, nil);
938 [self appendToResponse:response inContext:ctx];
939 WOContext_leaveComponent(ctx, self);
941 [ctx deleteLastElementIDComponent];
946 if ([[[ctx request] method] isEqualToString:@"HEAD"])
947 [response setContent:[NSData data]];
950 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
951 if ([[ctx application] isPageRefreshOnBacktrackEnabled])
952 [response disableClientCaching];
959 - (void)encodeWithCoder:(NSCoder *)_coder {
960 BOOL doesReloadTemplates = self->componentFlags.reloadTemplates;
962 [_coder encodeObject:self->wocBindings];
963 [_coder encodeObject:self->wocName];
964 [_coder encodeConditionalObject:self->parentComponent];
965 [_coder encodeObject:self->subcomponents];
966 [_coder encodeObject:self->wocVariables];
967 [_coder encodeConditionalObject:self->session];
968 [_coder encodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
970 - (id)initWithCoder:(NSCoder *)_decoder {
971 if ((self = [super init])) {
972 BOOL doesReloadTemplates = YES;
974 self->wocBindings = [[_decoder decodeObject] retain];
975 self->wocName = [[_decoder decodeObject] retain];
976 self->parentComponent = [_decoder decodeObject]; // non-retained
977 self->subcomponents = [[_decoder decodeObject] retain];
978 self->wocVariables = [[_decoder decodeObject] retain];
979 self->session = [_decoder decodeObject]; // non-retained
981 [_decoder decodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
982 [self setCachingEnabled:!doesReloadTemplates];
987 /* component variables */
989 - (BOOL)isStateless {
993 [self->wocVariables removeAllObjects];
996 - (void)setObject:(id)_obj forKey:(NSString *)_key {
997 _setExtraVar(self, _key, _obj);
999 - (id)objectForKey:(NSString *)_key {
1000 return _getExtraVar(self, _key);
1002 - (NSDictionary *)variableDictionary {
1003 return self->wocVariables;
1006 - (BOOL)logComponentVariableCreations {
1007 /* only if we have a subclass, we can store values in ivars ... */
1008 return (self->isa != WOComponentClass) ? YES : NO;
1011 #if !NG_USE_KVC_FALLBACK /* only override on libFoundation */
1013 - (void)takeValue:(id)_value forKey:(NSString *)_key {
1014 if (WOSetKVCValueUsingMethod(self, _key, _value)) {
1018 if (WOGetKVCGetMethod(self, _key) == NULL) {
1019 if (_value == nil) {
1021 [self debugWithFormat:
1022 @"storing <nil> value in component variable %@", _key];
1025 if ([self->wocVariables objectForKey:_key])
1026 [self setObject:nil forKey:_key];
1031 if ([self logComponentVariableCreations]) {
1032 /* only if we have a subclass, we can store values in ivars ... */
1033 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1034 [self debugWithFormat:
1035 @"Created component variable (class=%@): '%@'.",
1036 NSStringFromClass(self->isa), _key];
1041 [self setObject:_value forKey:_key];
1045 [self debugWithFormat:
1046 @"value %@ could not set via method or KVC "
1047 @"(self responds to %@: %s).",
1049 [self respondsToSelector:NSSelectorFromString(_key)] ? "yes" : "no"];
1054 - (id)valueForKey:(NSString *)_key {
1057 if ((value = WOGetKVCValueUsingMethod(self, _key)))
1061 [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1063 if ((value = [self objectForKey:_key]))
1069 #else /* use fallback methods on other Foundation libraries */
1071 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
1072 // Note: this is not used on libFoundation, insufficient KVC implementation
1074 if (_value == nil) {
1076 [self debugWithFormat:
1077 @"storing <nil> value in component variable %@", _key];
1080 if ([self->wocVariables objectForKey:_key])
1081 [self setObject:nil forKey:_key];
1087 if ([self logComponentVariableCreations]) {
1088 /* only if we have a subclass, we can store values in ivars ... */
1089 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1090 [self debugWithFormat:@"Created component variable (class=%@): '%@'.",
1091 NSStringFromClass(self->isa), _key];
1096 [self setObject:_value forKey:_key];
1098 - (id)valueForUndefinedKey:(NSString *)_key {
1099 // Note: this is not used on libFoundation, insufficient KVC implementation
1101 [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1103 return [self objectForKey:_key];
1106 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
1107 // deprecated: pre-Panther method
1108 [self setValue:_value forUndefinedKey:_key];
1110 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1111 // deprecated: pre-Panther method
1112 return [self valueForUndefinedKey:_key];
1115 - (void)unableToSetNilForKey:(NSString *)_key {
1116 // TODO: should we call setValue:NSNull forKey?
1117 [self errorWithFormat:@"unable to set 'nil' for key: '%@'", _key];
1122 - (void)validationFailedWithException:(NSException *)_exception
1123 value:(id)_value keyPath:(NSString *)_keyPath
1125 [self warnWithFormat:
1126 @"formatter failed for value %@ (keyPath=%@): %@",
1127 _value, _keyPath, [_exception reason]];
1132 - (BOOL)isEventLoggingEnabled {
1136 - (BOOL)isDebuggingEnabled {
1139 - (NSString *)loggingPrefix {
1143 if ([n length] == 0)
1144 return @"<component without name>";
1149 /* woo/plist unarchiving */
1151 - (id)unarchiver:(EOKeyValueUnarchiver *)_archiver
1152 objectForReference:(id)_keyPath
1154 return [self valueForKeyPath:_keyPath];
1159 - (id)copyWithZone:(NSZone *)_zone {
1160 // TODO: find out who triggers this
1161 return [self retain];
1166 - (NSString *)description {
1167 NSMutableString *str;
1170 str = [NSMutableString stringWithCapacity:128];
1171 [str appendFormat:@"<0x%08X[%@]: name=%@", self,
1172 NSStringFromClass([self class]), [self name]];
1174 if (self->parentComponent)
1175 [str appendFormat:@" parent=%@", [self->parentComponent name]];
1176 if (self->subcomponents)
1177 [str appendFormat:@" #subs=%i", [self->subcomponents count]];
1179 if (self->componentFlags.isAwake)
1180 [str appendFormat:@" awake=0x%08X", self->context];
1181 else if (self->context == nil)
1182 [str appendString:@" no-ctx"];
1184 if ((tmp = _getExtraVar(self, @"__worm")))
1185 [str appendFormat:@" rm=%@", tmp];
1187 [str appendString:@">"];
1191 @end /* WOComponent */
1193 @implementation WOComponent(Statistics)
1195 - (NSString *)descriptionForResponse:(WOResponse *)_response
1196 inContext:(WOContext *)_context
1201 @end /* WOComponent(Statistics) */
1203 @implementation WOComponent(AdvancedBindingAccessors)
1205 - (void)setUnsignedIntValue:(unsigned)_value forBinding:(NSString *)_name {
1206 [self setValue:[NSNumber numberWithUnsignedInt:_value] forBinding:_name];
1208 - (unsigned)unsignedIntValueForBinding:(NSString *)_name {
1209 return [[self valueForBinding:_name] unsignedIntValue];
1212 - (void)setIntValue:(int)_value forBinding:(NSString *)_name {
1213 [self setValue:[NSNumber numberWithInt:_value] forBinding:_name];
1215 - (int)intValueForBinding:(NSString *)_name {
1216 return [[self valueForBinding:_name] intValue];
1219 - (void)setBoolValue:(BOOL)_value forBinding:(NSString *)_name {
1220 [self setValue:[NSNumber numberWithBool:_value] forBinding:_name];
1222 - (BOOL)boolValueForBinding:(NSString *)_name {
1223 return [[self valueForBinding:_name] boolValue];
1226 #if !NG_USE_KVC_FALLBACK
1227 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1228 [self logWithFormat:@"query for unbound key: %@", _key];
1229 return [super handleQueryWithUnboundKey:_key];
1233 @end /* WOComponent(AdvancedBindingAccessors) */