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 #import <EOControl/EOControl.h>
36 #ifdef MULLE_EO_CONTROL
37 #import <EOControl/EOKeyValueUnarchiver.h>
39 #include <NGExtensions/NSString+Ext.h>
41 @interface WOContext(ComponentStackCount)
42 - (unsigned)componentStackCount;
45 #if APPLE_FOUNDATION_LIBRARY || NeXT_Foundation_LIBRARY
46 @interface NSObject(Miss)
47 - (id)notImplemented:(SEL)cmd;
51 #if !LIB_FOUNDATION_LIBRARY
52 # define NG_USE_KVC_FALLBACK 1
55 @implementation WOComponent
57 static Class NSDateClass = Nil;
58 static Class WOComponentClass = Nil;
60 static NGLogger *perfLogger = nil;
62 static BOOL debugOn = NO;
63 static BOOL debugComponentAwake = NO;
64 static BOOL debugTemplates = NO;
65 static BOOL debugTakeValues = NO;
66 static BOOL abortOnAwakeComponentInCtxDealloc = NO;
67 static BOOL abortOnMissingCtx = NO;
68 static BOOL wakeupPageOnCreation = NO;
71 // TODO: is really v4 for baseURL/cycleContext ivar changes
72 return [super version] + 0 /* v2 */;
77 static BOOL didInit = NO;
82 NSAssert2([super version] == 2,
83 @"invalid superclass (%@) version %i !",
84 NSStringFromClass([self superclass]), [super version]);
86 ud = [NSUserDefaults standardUserDefaults];
87 lm = [NGLoggerManager defaultLoggerManager];
89 WOComponentClass = [WOComponent class];
90 NSDateClass = [NSDate class];
91 perfLogger = [lm loggerForDefaultKey:@"WOProfileElements"];
92 debugOn = [WOApplication isDebuggingEnabled];
93 debugComponentAwake = [ud boolForKey:@"WODebugComponentAwake"];
95 if ((debugTakeValues = [ud boolForKey:@"WODebugTakeValues"]))
96 NSLog(@"WOComponent: WODebugTakeValues on.");
98 abortOnAwakeComponentInCtxDealloc =
99 [ud boolForKey:@"WOCoreOnAwakeComponentInCtxDealloc"];
103 if ((self = [super init])) {
104 NSNotificationCenter *nc;
105 WOComponentDefinition *cdef;
107 if ((cdef = (id)self->wocVariables)) {
108 // HACK CD, see WOComponentDefinition
109 self->wocVariables = nil;
112 if (self->wocName == nil)
113 self->wocName = [NSStringFromClass([self class]) copy];
115 [self setCachingEnabled:[[self application] isCachingEnabled]];
117 /* finish initialization */
120 [cdef _finishInitializingComponent:self];
121 [cdef release]; cdef = nil;
123 #if !APPLE_FOUNDATION_LIBRARY && !NeXT_Foundation_LIBRARY
125 /* this is triggered by Publisher on MacOSX */
126 [self debugWithFormat:
127 @"Note: got no component definition according to HACK CD"];
131 /* add to notification center */
133 nc = [NSNotificationCenter defaultCenter];
135 [nc addObserver:self selector:@selector(_sessionWillDealloc:)
136 name:@"WOSessionWillDeallocate" object:nil];
138 [nc addObserver:self selector:@selector(_contextWillDealloc:)
139 name:@"WOContextWillDeallocate" object:nil];
143 - (id)initWithContext:(WOContext *)_ctx {
144 [self _setContext:_ctx];
145 if ((self = [self init])) {
146 if (self->context != nil)
147 [self ensureAwakeInContext:self->context];
149 [self warnWithFormat:
150 @"no context given to -initWithContext: ..."];
157 [[NSNotificationCenter defaultCenter] removeObserver:self];
159 [[self->subcomponents allValues]
160 makeObjectsPerformSelector:@selector(setParent:)
162 [self->subcomponents release];
164 [self->wocClientObject release];
165 [self->wocBindings release];
166 [self->wocVariables release];
167 [self->wocName release];
168 [self->wocBaseURL release];
172 static inline void _setExtraVar(WOComponent *self, NSString *_key, id _obj) {
174 if (self->wocVariables == nil)
175 self->wocVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
177 [self->wocVariables setObject:_obj forKey:_key];
180 [self->wocVariables removeObjectForKey:_key];
182 static inline id _getExtraVar(WOComponent *self, NSString *_key) {
183 return [self->wocVariables objectForKey:_key];
188 - (void)_sessionWillDealloc:(NSNotification *)_notification {
190 NSAssert(_notification, @"missing valid session arg ...");
193 if (self->session == nil) {
194 /* component isn't interested in session anymore anyway ... */
197 if (self->session != [_notification object])
198 /* not the component's context ... */
202 [self debugWithFormat:@"resetting sn/ctx because session will dealloc .."];
205 if (self->componentFlags.isAwake) {
206 [self warnWithFormat:
207 @"session will dealloc, but component 0x%p is awake (ctx=%@) !",
208 self, self->context];
209 [self _sleepWithContext:self->context];
213 [self _setContext:nil];
215 - (void)_contextWillDealloc:(NSNotification *)_notification {
217 NSAssert(_notification, @"missing valid notification arg ...");
220 if (self->context == nil)
221 /* component isn't interested in context anyway ... */
223 if (![[self->context contextID] isEqualToString:[_notification object]])
224 /* not the component's context ... */
228 [self debugWithFormat:@"resetting sn/ctx because context will dealloc .."];
231 if (self->componentFlags.isAwake) {
233 Note: this is not necessarily a problem, no specific reason to log
236 [self debugWithFormat:
237 @"context %@ will dealloc, but component is awake in ctx %@!",
238 [_notification object], [self->context contextID]];
239 if (abortOnAwakeComponentInCtxDealloc)
242 [self _sleepWithContext:nil];
245 [self _setContext:nil];
255 if (self->componentFlags.isAwake) {
256 [self warnWithFormat:
257 @"component should not be awake if sleep is called !"];
259 if (self->context == nil) {
260 [self warnWithFormat:
261 @"context should not be nil if sleep is called !"];
265 self->componentFlags.isAwake = 0;
266 [self _setContext:nil];
267 self->application = nil;
271 - (void)ensureAwakeInContext:(WOContext *)_ctx {
273 NSAssert1(_ctx, @"missing context for awake (component=%@) ...", self);
276 if (debugComponentAwake)
277 [self debugWithFormat:@"0x%p ensureAwakeInContext:0x%p", self, _ctx];
281 if (self->componentFlags.isAwake) {
282 if (self->context == _ctx) {
283 if (debugComponentAwake)
284 [self debugWithFormat:@"0x%p already awake:0x%p", self, _ctx];
291 if (self->context == nil) [self _setContext:_ctx];
292 if (self->application == nil) self->application = [_ctx application];
294 if ((self->session == nil) && [_ctx hasSession])
295 self->session = [_ctx session];
297 self->componentFlags.isAwake = 1;
298 [_ctx _addAwakeComponent:self]; /* ensure that sleep is called */
300 /* awake subcomponents */
302 NSEnumerator *children;
305 children = [self->subcomponents objectEnumerator];
306 while ((child = [children nextObject]) != nil)
307 [child _awakeWithContext:_ctx];
313 - (void)_awakeWithContext:(WOContext *)_ctx {
314 if (self->componentFlags.isAwake)
317 [self ensureAwakeInContext:_ctx];
319 - (void)_sleepWithContext:(WOContext *)_ctx {
320 if (debugComponentAwake)
321 [self debugWithFormat:@"0x%p _sleepWithContext:0x%p", self, _ctx];
323 if (_ctx != self->context) {
324 if ((self->context != nil) && (_ctx != nil)) {
325 /* component is active in different context ... */
326 [self warnWithFormat:
327 @"sleep context mismatch (own=0x%p vs given=0x%p)",
328 self->context, _ctx];
333 if (self->componentFlags.isAwake) {
335 Sleep all child components, this is necessary to ensure some ordering
336 in the sleep calls. All awake components are put to sleep in any case
337 by the WOContext destructor.
339 NSEnumerator *children;
342 children = [self->subcomponents objectEnumerator];
343 self->componentFlags.isAwake = 0;
345 while ((child = [children nextObject]))
346 [child _sleepWithContext:_ctx];
350 [self _setContext:nil];
351 self->application = nil;
358 return self->wocName;
360 - (NSString *)frameworkName {
363 cbundle = [NGBundle bundleForClass:[self class]];
364 if (cbundle == [NSBundle mainBundle])
367 return [cbundle bundleName];
370 NSArray *languages = nil;
372 #if 0 // the component might not yet be awake !
373 languages = [[self context] resourceLookupLanguages];
376 return [[self resourceManager]
377 pathToComponentNamed:[self name]
378 inFramework:[self frameworkName]
379 languages:languages];
381 - (void)setBaseURL:(NSURL *)_url {
382 ASSIGNCOPY(self->wocBaseURL, _url);
387 if (self->wocBaseURL)
388 return self->wocBaseURL;
390 url = [(WOApplication *)[self application] baseURL];
392 [[NSURL URLWithString:@"WebServerResources" relativeToURL:url] copy];
393 return self->wocBaseURL;
396 - (NSString *)componentActionURLForContext:(WOContext *)_ctx {
397 return [@"/" stringByAppendingString:[self name]];
401 if (self->application == nil)
402 return (self->application = [WOApplication application]);
403 return self->application;
406 - (id)existingSession {
407 if (self->session != nil)
408 return self->session;
410 if ([[self context] hasSession])
411 return [self session];
416 if (self->session == nil) {
417 if ((self->session = [[self context] session]) == nil) {
418 [self debugWithFormat:@"could not get session object from context %@",
423 if (self->session == nil)
424 [self warnWithFormat:@"missing session for component!"];
426 return self->session;
429 - (void)_setContext:(WOContext *)_ctx {
430 self->context = _ctx;
432 - (WOContext *)context {
433 if (self->context != nil)
434 return self->context;
436 [self debugWithFormat:
437 @"missing context in component 0x%p (component%s)",
439 self->componentFlags.isAwake ? " is awake" : " is not awake"];
440 if (abortOnMissingCtx) {
441 [self errorWithFormat:@"aborting, because ctx is missing !"];
445 if (self->application == nil)
446 self->application = [WOApplication application];
447 [self _setContext:[self->application context]];
449 if (self->context == nil)
450 [self warnWithFormat:@"could not determine context object!"];
452 return self->context;
456 return [[self context] hasSession];
459 - (void)setCachingEnabled:(BOOL)_flag {
460 self->componentFlags.reloadTemplates = _flag ? 0 : 1;
462 - (BOOL)isCachingEnabled {
463 return (!self->componentFlags.reloadTemplates) ? YES : NO;
466 - (id)pageWithName:(NSString *)_name {
468 WOResourceManager *rm;
469 WOComponent *component;
471 languages = [[self context] resourceLookupLanguages];
472 rm = [self resourceManager];
475 Note: this API is somewhat broken since the component expects the
476 -initWithContext: message for initialization yet we pass no
479 component = [rm pageWithName:_name languages:languages];
481 // Note: should we call ensureAwakeInContext or is this to early ?
482 // probably the component should be woken up if it enters the ctx.
483 // If we create a page but never render it, we may get a warning
484 // that a context will dealloc but the page is active (yet not awake)
485 // Note: awake is not the same like "has context"! A component can have a
486 // context without being awake - maybe we need an additional method
487 // to hook up a component but the awake list
488 if (wakeupPageOnCreation)
489 [component ensureAwakeInContext:[self context]];
493 - (NSString *)stringForKey:(NSString *)_key
494 inTableNamed:(NSString *)_tableName
495 withDefaultValue:(NSString *)_default
500 langs = [[self context] resourceLookupLanguages];
502 return [[[self application]
505 inTableNamed:_tableName
506 withDefaultValue:_default
510 - (void)setName:(NSString *)_name {
511 if (![_name isNotNull])
512 [self warnWithFormat:@"setting 'nil' name on component!"];
514 ASSIGNCOPY(self->wocName, _name);
517 - (void)setBindings:(NSDictionary *)_bindings {
518 // this is _very_ private and used by WOComponentReference
519 ASSIGNCOPY(self->wocBindings, _bindings);
521 - (NSDictionary *)_bindings {
523 return self->wocBindings;
526 - (void)setSubComponents:(NSDictionary *)_dictionary {
527 ASSIGNCOPY(self->subcomponents, _dictionary);
529 - (NSDictionary *)_subComponents {
531 return self->subcomponents;
534 - (void)setParent:(WOComponent *)_parent {
535 self->parentComponent = _parent;
538 return self->parentComponent;
541 /* language change */
543 - (void)languageArrayDidChange {
548 - (NSString *)elementID {
554 - (id<WOActionResults>)redirectToLocation:(id)_loc {
555 WOContext *ctx = [self context];
562 if ((r = [ctx response]) == nil)
563 r = [[[WOResponse alloc] init] autorelease];
565 if ([_loc isKindOfClass:[NSURL class]])
566 target = [_loc absoluteString];
568 _loc = [_loc stringValue];
569 if ([_loc isAbsoluteURL])
571 else if ([_loc isAbsolutePath])
574 target = [[ctx request] uri];
576 // TODO: check whether the algorithm is correct
577 if (![target hasSuffix:@"/"])
578 target = [target stringByDeletingLastPathComponent];
579 target = [target stringByAppendingPathComponent:_loc];
585 [r setStatus:302 /* moved */];
586 [r setHeader:target forKey:@"location"];
590 - (void)setResourceManager:(WOResourceManager *)_rm {
591 _setExtraVar(self, @"__worm", _rm);
593 - (WOResourceManager *)resourceManager {
594 WOResourceManager *rm;
597 if ((rm = _getExtraVar(self, @"__worm")))
600 /* ask parent component ... */
601 if ((p = [self parent])) {
602 NSAssert2(p != self, @"parent component == component !!! (%@ vs %@)",
604 if ((rm = [p resourceManager]))
608 /* ask application ... */
609 return [[self application] resourceManager];
612 - (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_ext {
614 NSEnumerator *languages;
616 BOOL isDirectory = NO;
619 if (_ext) _name = [_name stringByAppendingPathExtension:_ext];
621 if ((cpath = [self path]) == nil) {
622 [self warnWithFormat:@"no path set in component %@", [self name]];
626 fm = [NSFileManager defaultManager];
627 if (![fm fileExistsAtPath:cpath isDirectory:&isDirectory]) {
628 [self warnWithFormat:@"component directory %@ does not exist !", cpath];
632 [self warnWithFormat:@"component path %@ is not a directory !", cpath];
636 /* check in language projects */
638 languages = [self hasSession]
639 ? [[(WOSession *)[self session] languages] objectEnumerator]
640 : [[[[self context] request] browserLanguages] objectEnumerator];
642 while ((language = [languages nextObject]) != nil) {
643 language = [language stringByAppendingPathExtension:@"lproj"];
644 language = [cpath stringByAppendingPathComponent:language];
645 language = [language stringByAppendingPathExtension:_name];
647 if ([fm fileExistsAtPath:language])
651 /* check in component */
652 cpath = [cpath stringByAppendingPathComponent:_name];
653 if ([fm fileExistsAtPath:cpath])
661 + (WOElement *)templateWithHTMLString:(NSString *)_html
662 declarationString:(NSString *)_wod
663 languages:(NSArray *)_languages
665 return [self notImplemented:_cmd];
667 - (WOElement *)templateWithHTMLString:(NSString *)_html
668 declarationString:(NSString *)_wod
671 return [[self class] templateWithHTMLString:_html
672 declarationString:_wod
673 languages:[(WOSession *)[self session] languages]];
676 - (WOElement *)templateWithName:(NSString *)_name {
677 WOResourceManager *resourceManager;
681 if ((resourceManager = [self resourceManager]) == nil) {
682 [self errorWithFormat:@"%s: could not determine resource manager !",
683 __PRETTY_FUNCTION__];
687 languages = [[self context] resourceLookupLanguages];
688 tmpl = [resourceManager templateWithName:_name languages:languages];
690 if (debugTemplates) [self debugWithFormat:@"found template: %@", tmpl];
694 - (void)setTemplate:(id)_template {
696 WO has private API for this:
697 - (void)setTemplate:(WOElement *)template;
698 As mentioned in the OmniGroup WO mailing list ...
700 _setExtraVar(self, @"__wotemplate", _template);
702 - (WOElement *)_woComponentTemplate {
705 // TODO: move to ivar?
706 if ((element = _getExtraVar(self, @"__wotemplate")) != nil)
709 return [self templateWithName:[self name]];
712 /* child components */
714 - (WOComponent *)childComponentWithName:(NSString *)_name {
717 child = [self->subcomponents objectForKey:_name];
718 if ([child isComponentFault]) {
719 NSMutableDictionary *tmp;
721 child = [child resolveWithParent:self];
723 [self warnWithFormat:@"Could not resolve component fault: %@", _name];
727 tmp = [self->subcomponents mutableCopy];
728 [tmp setObject:child forKey:_name];
729 [self->subcomponents release]; self->subcomponents = nil;
730 self->subcomponents = [tmp copy];
731 [tmp release]; tmp = nil;
736 - (BOOL)synchronizesVariablesWithBindings {
737 return [self isStateless] ? NO : YES;
740 - (void)setValue:(id)_value forBinding:(NSString *)_name {
743 WODynamicElement *content;
745 ctx = [self context];
746 parent = [ctx parentComponent];
747 content = [ctx componentContent];
750 parent = [self parent];
751 [self warnWithFormat:@"tried to set value of binding '%@' in component "
752 @"'%@' without parent component (parent is '%@') !",
753 _name, [self name], [parent name]];
756 [[self retain] autorelease];
757 [[content retain] autorelease];
759 WOContext_leaveComponent(ctx, self);
760 [[self->wocBindings objectForKey:_name] setValue:_value inComponent:parent];
761 WOContext_enterComponent(ctx, self, content);
763 - (id)valueForBinding:(NSString *)_name {
766 WODynamicElement *content;
769 ctx = [self context];
770 parent = [ctx parentComponent];
771 content = [ctx componentContent];
774 parent = [self parent];
775 [self warnWithFormat:@"tried to retrieve value of binding '%@' in"
776 @" component '%@' without parent component (parent is '%@') !",
777 _name, [self name], [parent name]];
780 [[self retain] autorelease];
781 [[content retain] autorelease];
783 WOContext_leaveComponent(ctx, self);
784 value = [[self->wocBindings objectForKey:_name] valueInComponent:parent];
785 WOContext_enterComponent(ctx, self, content);
790 - (BOOL)hasBinding:(NSString *)_name {
791 return ([self->wocBindings objectForKey:_name] != nil) ? YES : NO;
794 - (BOOL)canSetValueForBinding:(NSString *)_name {
795 WOAssociation *binding;
797 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
800 return [binding isValueSettable];
802 - (BOOL)canGetValueForBinding:(NSString *)_name {
803 WOAssociation *binding;
805 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
811 - (id)performParentAction:(NSString *)_name {
814 WODynamicElement *content;
818 ctx = [self context];
819 parent = [ctx parentComponent];
820 content = [ctx componentContent];
821 action = NSSelectorFromString(_name);
823 if (parent == nil) return nil;
824 if (action == NULL) return nil;
826 NSAssert(parent != self, @"parent component equals current component");
828 if (![parent respondsToSelector:action]) {
829 [self debugWithFormat:@"parent %@ doesn't respond to %@",
830 [parent name], _name];
834 self = [self retain];
836 WOContext_leaveComponent(ctx, self);
837 *(&result) = [parent performSelector:action];
838 WOContext_enterComponent(ctx, self, content);
842 [localException raise];
852 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c {
854 [self debugWithFormat:@"%s: default says no.", __PRETTY_FUNCTION__];
858 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
859 WOElement *template = nil;
862 [self debugWithFormat:@"take values from rq 0x%p", _req];
864 NSAssert1(self->componentFlags.isAwake,
865 @"component %@ is not awake !", self);
867 [self _setContext:_ctx];
868 template = [self _woComponentTemplate];
870 if (template == nil) {
872 [self debugWithFormat:@"cannot take values, component has no template!"];
876 if (template->takeValues) {
877 template->takeValues(template,
878 @selector(takeValuesFromRequest:inContext:),
882 [template takeValuesFromRequest:_req inContext:_ctx];
885 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
886 WOElement *template = nil;
889 NSAssert1(self->componentFlags.isAwake, @"component %@ is not awake!", self);
891 [self _setContext:_ctx];
892 template = [self _woComponentTemplate];
893 result = [template invokeActionForRequest:_req inContext:_ctx];
897 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
898 WOElement *template = nil;
899 NSTimeInterval st = 0.0;
901 NSAssert1(self->componentFlags.isAwake,
902 @"component %@ is not awake !", self);
904 if (self->context != _ctx) {
905 [self debugWithFormat:@"%s: component ctx != ctx (%@ vs %@)",
906 __PRETTY_FUNCTION__, self->context, _ctx];
910 [self _setContext:_ctx];
912 if ((template = [self _woComponentTemplate]) == nil) {
914 [self debugWithFormat:@"component has no template (rm=%@).",
915 [self resourceManager]];
921 st = [[NSDateClass date] timeIntervalSince1970];
923 if (template->appendResponse) {
924 template->appendResponse(template,
925 @selector(appendToResponse:inContext:),
929 [template appendToResponse:_response inContext:_ctx];
934 diff = [[NSDateClass date] timeIntervalSince1970] - st;
936 for (i = [_ctx componentStackCount]; i >= 0; i--)
939 [perfLogger logWithFormat:@"Template %@ (comp %@): %0.3fs\n",
946 /* WOActionResults */
948 - (WOResponse *)generateResponse {
949 WOResponse *response = nil;
950 WOContext *ctx = nil;
953 ctx = [self context];
954 ctxID = [ctx contextID];
955 response = [WOResponse responseWithRequest:[ctx request]];
958 [self debugWithFormat:@"missing ctx-id for context %@", ctx];
962 [ctx deleteAllElementIDComponents];
963 [ctx appendElementIDComponent:ctxID];
965 WOContext_enterComponent(ctx, self, nil);
966 [self appendToResponse:response inContext:ctx];
967 WOContext_leaveComponent(ctx, self);
969 [ctx deleteLastElementIDComponent];
974 if ([[[ctx request] method] isEqualToString:@"HEAD"])
975 [response setContent:[NSData data]];
978 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
979 if ([[ctx application] isPageRefreshOnBacktrackEnabled])
980 [response disableClientCaching];
987 - (void)encodeWithCoder:(NSCoder *)_coder {
988 BOOL doesReloadTemplates = self->componentFlags.reloadTemplates;
990 [_coder encodeObject:self->wocBindings];
991 [_coder encodeObject:self->wocName];
992 [_coder encodeConditionalObject:self->parentComponent];
993 [_coder encodeObject:self->subcomponents];
994 [_coder encodeObject:self->wocVariables];
995 [_coder encodeConditionalObject:self->session];
996 [_coder encodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
998 - (id)initWithCoder:(NSCoder *)_decoder {
999 if ((self = [super init])) {
1000 BOOL doesReloadTemplates = YES;
1002 self->wocBindings = [[_decoder decodeObject] retain];
1003 self->wocName = [[_decoder decodeObject] retain];
1004 self->parentComponent = [_decoder decodeObject]; // non-retained
1005 self->subcomponents = [[_decoder decodeObject] retain];
1006 self->wocVariables = [[_decoder decodeObject] retain];
1007 self->session = [_decoder decodeObject]; // non-retained
1009 [_decoder decodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
1010 [self setCachingEnabled:!doesReloadTemplates];
1015 /* component variables */
1017 - (BOOL)isStateless {
1021 [self->wocVariables removeAllObjects];
1024 - (void)setObject:(id)_obj forKey:(NSString *)_key {
1025 _setExtraVar(self, _key, _obj);
1027 - (id)objectForKey:(NSString *)_key {
1028 return _getExtraVar(self, _key);
1030 - (NSDictionary *)variableDictionary {
1031 return self->wocVariables;
1034 - (BOOL)logComponentVariableCreations {
1035 /* only if we have a subclass, we can store values in ivars ... */
1036 return (self->isa != WOComponentClass) ? YES : NO;
1039 #if !NG_USE_KVC_FALLBACK /* only override on libFoundation */
1041 - (void)takeValue:(id)_value forKey:(NSString *)_key {
1042 if (WOSetKVCValueUsingMethod(self, _key, _value)) {
1046 if (WOGetKVCGetMethod(self, _key) == NULL) {
1047 if (_value == nil) {
1049 [self debugWithFormat:
1050 @"storing <nil> value in component variable %@", _key];
1053 if ([self->wocVariables objectForKey:_key])
1054 [self setObject:nil forKey:_key];
1059 if ([self logComponentVariableCreations]) {
1060 /* only if we have a subclass, we can store values in ivars ... */
1061 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1062 [self debugWithFormat:
1063 @"Created component variable (class=%@): '%@'.",
1064 NSStringFromClass(self->isa), _key];
1069 [self setObject:_value forKey:_key];
1073 [self debugWithFormat:
1074 @"value %@ could not set via method or KVC "
1075 @"(self responds to %@: %s).",
1077 [self respondsToSelector:NSSelectorFromString(_key)] ? "yes" : "no"];
1082 - (id)valueForKey:(NSString *)_key {
1085 if ((value = WOGetKVCValueUsingMethod(self, _key)))
1089 [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1091 if ((value = [self objectForKey:_key]) != nil)
1097 #else /* use fallback methods on other Foundation libraries */
1099 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
1100 // Note: this is not used on libFoundation, insufficient KVC implementation
1103 [self logWithFormat:@"KVC: set the component variable %@: %@",_key,_value];
1106 if (_value == nil) {
1108 [self debugWithFormat:
1109 @"storing <nil> value in component variable %@", _key];
1112 if ([self->wocVariables objectForKey:_key] != nil)
1113 [self setObject:nil forKey:_key];
1119 if ([self logComponentVariableCreations]) {
1120 /* only if we have a subclass, we can store values in ivars ... */
1121 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1122 [self debugWithFormat:@"Created component variable (class=%@): '%@'.",
1123 NSStringFromClass(self->isa), _key];
1127 [self setObject:_value forKey:_key];
1130 - (id)valueForUndefinedKey:(NSString *)_key {
1131 // Note: this is not used on libFoundation, insufficient KVC implementation
1133 [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1136 return [self objectForKey:_key];
1139 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
1140 // deprecated: pre-Panther method
1141 [self setValue:_value forUndefinedKey:_key];
1143 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1144 // deprecated: pre-Panther method
1145 return [self valueForUndefinedKey:_key];
1148 - (void)unableToSetNilForKey:(NSString *)_key {
1149 // TODO: should we call setValue:NSNull forKey?
1150 [self errorWithFormat:@"unable to set 'nil' for key: '%@'", _key];
1155 - (void)validationFailedWithException:(NSException *)_exception
1156 value:(id)_value keyPath:(NSString *)_keyPath
1158 [self warnWithFormat:
1159 @"formatter failed for value %@ (keyPath=%@): %@",
1160 _value, _keyPath, [_exception reason]];
1165 - (BOOL)isEventLoggingEnabled {
1169 - (BOOL)isDebuggingEnabled {
1172 - (NSString *)loggingPrefix {
1176 if ([n length] == 0)
1177 return @"<component without name>";
1182 /* woo/plist unarchiving */
1184 - (id)unarchiver:(EOKeyValueUnarchiver *)_archiver
1185 objectForReference:(id)_keyPath
1188 This is used when a .woo file is unarchived. Eg datasources contain
1189 bindings in the archive:
1191 editingContext = session.defaultEditingContext;
1193 The binding will evaluate against the component during loading.
1195 return [self valueForKeyPath:_keyPath];
1200 - (id)copyWithZone:(NSZone *)_zone {
1201 // TODO: find out who triggers this
1202 return [self retain];
1207 - (NSString *)description {
1208 NSMutableString *str;
1211 str = [NSMutableString stringWithCapacity:128];
1212 [str appendFormat:@"<0x%p[%@]: name=%@", self,
1213 NSStringFromClass([self class]), [self name]];
1215 if (self->parentComponent)
1216 [str appendFormat:@" parent=%@", [self->parentComponent name]];
1217 if (self->subcomponents)
1218 [str appendFormat:@" #subs=%i", [self->subcomponents count]];
1220 if (self->componentFlags.isAwake)
1221 [str appendFormat:@" awake=0x%p", self->context];
1222 else if (self->context == nil)
1223 [str appendString:@" no-ctx"];
1225 if ((tmp = _getExtraVar(self, @"__worm")))
1226 [str appendFormat:@" rm=%@", tmp];
1228 [str appendString:@">"];
1234 - (NSString *)descriptionForResponse:(WOResponse *)_response
1235 inContext:(WOContext *)_context
1240 /* AdvancedBindingAccessors */
1242 - (void)setUnsignedIntValue:(unsigned)_value forBinding:(NSString *)_name {
1243 [self setValue:[NSNumber numberWithUnsignedInt:_value] forBinding:_name];
1245 - (unsigned)unsignedIntValueForBinding:(NSString *)_name {
1246 return [[self valueForBinding:_name] unsignedIntValue];
1249 - (void)setIntValue:(int)_value forBinding:(NSString *)_name {
1250 [self setValue:[NSNumber numberWithInt:_value] forBinding:_name];
1252 - (int)intValueForBinding:(NSString *)_name {
1253 return [[self valueForBinding:_name] intValue];
1256 - (void)setBoolValue:(BOOL)_value forBinding:(NSString *)_name {
1257 [self setValue:[NSNumber numberWithBool:_value] forBinding:_name];
1259 - (BOOL)boolValueForBinding:(NSString *)_name {
1260 return [[self valueForBinding:_name] boolValue];
1263 #if !NG_USE_KVC_FALLBACK
1264 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1265 [self logWithFormat:@"query for unbound key: %@", _key];
1266 return [super handleQueryWithUnboundKey:_key];
1270 @end /* WOComponent */