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 debugTakeValues = NO;
63 static BOOL abortOnAwakeComponentInCtxDealloc = NO;
64 static BOOL abortOnMissingCtx = NO;
65 static BOOL wakeupPageOnCreation = NO;
68 // TODO: is really v4 for baseURL/cycleContext ivar changes
69 return [super version] + 0 /* v2 */;
74 static BOOL didInit = NO;
79 NSAssert2([super version] == 2,
80 @"invalid superclass (%@) version %i !",
81 NSStringFromClass([self superclass]), [super version]);
83 ud = [NSUserDefaults standardUserDefaults];
84 lm = [NGLoggerManager defaultLoggerManager];
86 WOComponentClass = [WOComponent class];
87 NSDateClass = [NSDate class];
88 perfLogger = [lm loggerForDefaultKey:@"WOProfileElements"];
89 debugOn = [WOApplication isDebuggingEnabled];
90 debugComponentAwake = [ud boolForKey:@"WODebugComponentAwake"];
92 if ((debugTakeValues = [ud boolForKey:@"WODebugTakeValues"]))
93 NSLog(@"WOComponent: WODebugTakeValues on.");
95 abortOnAwakeComponentInCtxDealloc =
96 [ud boolForKey:@"WOCoreOnAwakeComponentInCtxDealloc"];
100 if ((self = [super init])) {
101 NSNotificationCenter *nc;
102 WOComponentDefinition *cdef;
104 if ((cdef = (id)self->wocVariables)) {
105 // HACK CD, see WOComponentDefinition
106 self->wocVariables = nil;
109 if (self->wocName == nil)
110 self->wocName = [NSStringFromClass([self class]) copy];
112 [self setCachingEnabled:[[self application] isCachingEnabled]];
114 /* finish initialization */
117 [cdef _finishInitializingComponent:self];
118 [cdef release]; cdef = nil;
120 #if !APPLE_FOUNDATION_LIBRARY && !NeXT_Foundation_LIBRARY
122 /* this is triggered by Publisher on MacOSX */
123 [self debugWithFormat:
124 @"Note: got no component definition according to HACK CD"];
128 /* add to notification center */
130 nc = [NSNotificationCenter defaultCenter];
132 [nc addObserver:self selector:@selector(_sessionWillDealloc:)
133 name:@"WOSessionWillDeallocate" object:nil];
135 [nc addObserver:self selector:@selector(_contextWillDealloc:)
136 name:@"WOContextWillDeallocate" object:nil];
140 - (id)initWithContext:(WOContext *)_ctx {
141 [self _setContext:_ctx];
142 if ((self = [self init])) {
143 if (self->context != nil)
144 [self ensureAwakeInContext:self->context];
146 [self warnWithFormat:
147 @"no context given to -initWithContext: ..."];
154 [[NSNotificationCenter defaultCenter] removeObserver:self];
156 [[self->subcomponents allValues]
157 makeObjectsPerformSelector:@selector(setParent:)
159 [self->subcomponents release];
161 [self->wocClientObject release];
162 [self->wocBindings release];
163 [self->wocVariables release];
164 [self->wocName release];
165 [self->wocBaseURL release];
169 static inline void _setExtraVar(WOComponent *self, NSString *_key, id _obj) {
171 if (self->wocVariables == nil)
172 self->wocVariables = [[NSMutableDictionary alloc] initWithCapacity:16];
174 [self->wocVariables setObject:_obj forKey:_key];
177 [self->wocVariables removeObjectForKey:_key];
179 static inline id _getExtraVar(WOComponent *self, NSString *_key) {
180 return [self->wocVariables objectForKey:_key];
185 - (void)_sessionWillDealloc:(NSNotification *)_notification {
187 NSAssert(_notification, @"missing valid session arg ...");
190 if (self->session == nil) {
191 /* component isn't interested in session anymore anyway ... */
194 if (self->session != [_notification object])
195 /* not the component's context ... */
199 [self debugWithFormat:@"resetting sn/ctx because session will dealloc .."];
202 if (self->componentFlags.isAwake) {
203 [self warnWithFormat:
204 @"session will dealloc, but component 0x%08X is awake (ctx=%@) !",
205 self, self->context];
206 [self _sleepWithContext:self->context];
210 [self _setContext:nil];
212 - (void)_contextWillDealloc:(NSNotification *)_notification {
214 NSAssert(_notification, @"missing valid notification arg ...");
217 if (self->context == nil)
218 /* component isn't interested in context anyway ... */
220 if (![[self->context contextID] isEqualToString:[_notification object]])
221 /* not the component's context ... */
225 [self debugWithFormat:@"resetting sn/ctx because context will dealloc .."];
228 if (self->componentFlags.isAwake) {
230 Note: this is not necessarily a problem, no specific reason to log
233 [self debugWithFormat:
234 @"context %@ will dealloc, but component is awake in ctx %@!",
235 [_notification object], [self->context contextID]];
236 if (abortOnAwakeComponentInCtxDealloc)
239 [self _sleepWithContext:nil];
242 [self _setContext:nil];
252 if (self->componentFlags.isAwake) {
253 [self warnWithFormat:
254 @"component should not be awake if sleep is called !"];
256 if (self->context == nil) {
257 [self warnWithFormat:
258 @"context should not be nil if sleep is called !"];
262 self->componentFlags.isAwake = 0;
263 [self _setContext:nil];
264 self->application = nil;
268 - (void)ensureAwakeInContext:(WOContext *)_ctx {
270 NSAssert1(_ctx, @"missing context for awake (component=%@) ...", self);
273 if (debugComponentAwake)
274 [self debugWithFormat:@"0x%08X ensureAwakeInContext:0x%08X", self, _ctx];
278 if (self->componentFlags.isAwake) {
279 if (self->context == _ctx) {
280 if (debugComponentAwake)
281 [self debugWithFormat:@"0x%08X already awake:0x%08X", self, _ctx];
288 if (self->context == nil) [self _setContext:_ctx];
289 if (self->application == nil) self->application = [_ctx application];
291 if ((self->session == nil) && [_ctx hasSession])
292 self->session = [_ctx session];
294 self->componentFlags.isAwake = 1;
295 [_ctx _addAwakeComponent:self]; /* ensure that sleep is called */
297 /* awake subcomponents */
299 NSEnumerator *children;
302 children = [self->subcomponents objectEnumerator];
303 while ((child = [children nextObject]) != nil)
304 [child _awakeWithContext:_ctx];
310 - (void)_awakeWithContext:(WOContext *)_ctx {
311 if (self->componentFlags.isAwake)
314 [self ensureAwakeInContext:_ctx];
316 - (void)_sleepWithContext:(WOContext *)_ctx {
317 if (debugComponentAwake)
318 [self debugWithFormat:@"0x%08X _sleepWithContext:0x%08X", self, _ctx];
320 if (_ctx != self->context) {
321 if ((self->context != nil) && (_ctx != nil)) {
322 /* component is active in different context ... */
323 [self warnWithFormat:
324 @"sleep context mismatch (own=0x%08X vs given=0x%08X)",
325 self->context, _ctx];
330 if (self->componentFlags.isAwake) {
332 Sleep all child components, this is necessary to ensure some ordering
333 in the sleep calls. All awake components are put to sleep in any case
334 by the WOContext destructor.
336 NSEnumerator *children;
339 children = [self->subcomponents objectEnumerator];
340 self->componentFlags.isAwake = 0;
342 while ((child = [children nextObject]))
343 [child _sleepWithContext:_ctx];
347 [self _setContext:nil];
348 self->application = nil;
355 return self->wocName;
357 - (NSString *)frameworkName {
360 cbundle = [NGBundle bundleForClass:[self class]];
361 if (cbundle == [NSBundle mainBundle])
364 return [cbundle bundleName];
367 NSArray *languages = nil;
369 #if 0 // the component might not yet be awake !
370 languages = [[self context] resourceLookupLanguages];
373 return [[self resourceManager]
374 pathToComponentNamed:[self name]
375 inFramework:[self frameworkName]
376 languages:languages];
378 - (void)setBaseURL:(NSURL *)_url {
379 ASSIGNCOPY(self->wocBaseURL, _url);
384 if (self->wocBaseURL)
385 return self->wocBaseURL;
387 url = [(WOApplication *)[self application] baseURL];
389 [[NSURL URLWithString:@"WebServerResources" relativeToURL:url] copy];
390 return self->wocBaseURL;
393 - (NSString *)componentActionURLForContext:(WOContext *)_ctx {
394 return [@"/" stringByAppendingString:[self name]];
398 if (self->application == nil)
399 return (self->application = [WOApplication application]);
400 return self->application;
403 - (id)existingSession {
405 return self->session;
407 if ([[self context] hasSession])
408 return [self session];
413 if (self->session == nil) {
414 if ((self->session = [[self context] session]) == nil) {
415 [self debugWithFormat:@"could not get session object from context %@",
420 if (self->session == nil)
421 [self warnWithFormat:@"missing session for component!"];
423 return self->session;
426 - (void)_setContext:(WOContext *)_ctx {
427 self->context = _ctx;
429 - (WOContext *)context {
431 return self->context;
433 [self debugWithFormat:
434 @"missing context in component 0x%08X (component%s)",
436 self->componentFlags.isAwake ? " is awake" : " is not awake"];
437 if (abortOnMissingCtx) {
438 [self errorWithFormat:@"aborting, because ctx is missing !"];
442 if (self->application == nil)
443 self->application = [WOApplication application];
444 [self _setContext:[self->application context]];
446 if (self->context == nil)
447 [self warnWithFormat:@"could not determine context object!"];
449 return self->context;
453 return [[self context] hasSession];
456 - (void)setCachingEnabled:(BOOL)_flag {
457 self->componentFlags.reloadTemplates = _flag ? 0 : 1;
459 - (BOOL)isCachingEnabled {
460 return (!self->componentFlags.reloadTemplates) ? YES : NO;
463 - (id)pageWithName:(NSString *)_name {
465 WOResourceManager *rm;
466 WOComponent *component;
468 languages = [[self context] resourceLookupLanguages];
469 rm = [self resourceManager];
472 Note: this API is somewhat broken since the component expects the
473 -initWithContext: message for initialization yet we pass no
476 component = [rm pageWithName:_name languages:languages];
478 // Note: should we call ensureAwakeInContext or is this to early ?
479 // probably the component should be woken up if it enters the ctx.
480 // If we create a page but never render it, we may get a warning
481 // that a context will dealloc but the page is active (yet not awake)
482 // Note: awake is not the same like "has context"! A component can have a
483 // context without being awake - maybe we need an additional method
484 // to hook up a component but the awake list
485 if (wakeupPageOnCreation)
486 [component ensureAwakeInContext:[self context]];
490 - (NSString *)stringForKey:(NSString *)_key
491 inTableNamed:(NSString *)_tableName
492 withDefaultValue:(NSString *)_default
497 langs = [[self context] resourceLookupLanguages];
499 return [[[self application]
502 inTableNamed:_tableName
503 withDefaultValue:_default
507 - (void)setName:(NSString *)_name {
508 if (![_name isNotNull])
509 [self warnWithFormat:@"setting 'nil' name on component!"];
511 ASSIGNCOPY(self->wocName, _name);
514 - (void)setBindings:(NSDictionary *)_bindings {
515 // this is _very_ private and used by WOComponentReference
516 ASSIGNCOPY(self->wocBindings, _bindings);
518 - (NSDictionary *)_bindings {
520 return self->wocBindings;
523 - (void)setSubComponents:(NSDictionary *)_dictionary {
524 ASSIGNCOPY(self->subcomponents, _dictionary);
526 - (NSDictionary *)_subComponents {
528 return self->subcomponents;
531 - (void)setParent:(WOComponent *)_parent {
532 self->parentComponent = _parent;
535 return self->parentComponent;
538 /* language change */
540 - (void)languageArrayDidChange {
545 - (NSString *)elementID {
551 - (id<WOActionResults>)redirectToLocation:(id)_loc {
552 WOContext *ctx = [self context];
559 if ((r = [ctx response]) == nil)
560 r = [[[WOResponse alloc] init] autorelease];
562 if ([_loc isKindOfClass:[NSURL class]])
563 target = [_loc absoluteString];
565 _loc = [_loc stringValue];
566 if ([_loc isAbsoluteURL])
568 else if ([_loc isAbsolutePath])
571 target = [[ctx request] uri];
573 // TODO: check whether the algorithm is correct
574 if (![target hasSuffix:@"/"])
575 target = [target stringByDeletingLastPathComponent];
576 target = [target stringByAppendingPathComponent:_loc];
582 [r setStatus:302 /* moved */];
583 [r setHeader:target forKey:@"location"];
587 - (void)setResourceManager:(WOResourceManager *)_rm {
588 _setExtraVar(self, @"__worm", _rm);
590 - (WOResourceManager *)resourceManager {
591 WOResourceManager *rm;
594 if ((rm = _getExtraVar(self, @"__worm")))
597 /* ask parent component ... */
598 if ((p = [self parent])) {
599 NSAssert2(p != self, @"parent component == component !!! (%@ vs %@)",
601 if ((rm = [p resourceManager]))
605 /* ask application ... */
606 return [[self application] resourceManager];
609 - (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_ext {
611 NSEnumerator *languages;
613 BOOL isDirectory = NO;
616 if (_ext) _name = [_name stringByAppendingPathExtension:_ext];
618 if ((cpath = [self path]) == nil) {
619 [self warnWithFormat:@"no path set in component %@", [self name]];
623 fm = [NSFileManager defaultManager];
624 if (![fm fileExistsAtPath:cpath isDirectory:&isDirectory]) {
625 [self warnWithFormat:@"component directory %@ does not exist !", cpath];
629 [self warnWithFormat:@"component path %@ is not a directory !", cpath];
633 /* check in language projects */
635 languages = [self hasSession]
636 ? [[(WOSession *)[self session] languages] objectEnumerator]
637 : [[[[self context] request] browserLanguages] objectEnumerator];
639 while ((language = [languages nextObject]) != nil) {
640 language = [language stringByAppendingPathExtension:@"lproj"];
641 language = [cpath stringByAppendingPathComponent:language];
642 language = [language stringByAppendingPathExtension:_name];
644 if ([fm fileExistsAtPath:language])
648 /* check in component */
649 cpath = [cpath stringByAppendingPathComponent:_name];
650 if ([fm fileExistsAtPath:cpath])
658 + (WOElement *)templateWithHTMLString:(NSString *)_html
659 declarationString:(NSString *)_wod
660 languages:(NSArray *)_languages
662 return [self notImplemented:_cmd];
664 - (WOElement *)templateWithHTMLString:(NSString *)_html
665 declarationString:(NSString *)_wod
668 return [[self class] templateWithHTMLString:_html
669 declarationString:_wod
670 languages:[(WOSession *)[self session] languages]];
673 - (WOElement *)templateWithName:(NSString *)_name {
674 WOResourceManager *resourceManager;
678 if ((resourceManager = [self resourceManager]) == nil) {
679 [self errorWithFormat:@"%s: could not determine resource manager !",
680 __PRETTY_FUNCTION__];
684 languages = [[self context] resourceLookupLanguages];
685 tmpl = [resourceManager templateWithName:_name languages:languages];
687 if (debugTemplates) [self debugWithFormat:@"found template: %@", tmpl];
691 - (void)setTemplate:(id)_template {
693 WO has private API for this:
694 - (void)setTemplate:(WOElement *)template;
695 As mentioned in the OmniGroup WO mailing list ...
697 _setExtraVar(self, @"__wotemplate", _template);
699 - (WOElement *)_woComponentTemplate {
702 // TODO: move to ivar?
703 if ((element = _getExtraVar(self, @"__wotemplate")) != nil)
706 return [self templateWithName:[self name]];
709 /* child components */
711 - (WOComponent *)childComponentWithName:(NSString *)_name {
714 child = [self->subcomponents objectForKey:_name];
715 if ([child isComponentFault]) {
716 NSMutableDictionary *tmp;
718 child = [child resolveWithParent:self];
720 [self warnWithFormat:@"Could not resolve component fault: %@", _name];
724 tmp = [self->subcomponents mutableCopy];
725 [tmp setObject:child forKey:_name];
726 [self->subcomponents release]; self->subcomponents = nil;
727 self->subcomponents = [tmp copy];
728 [tmp release]; tmp = nil;
733 - (BOOL)synchronizesVariablesWithBindings {
734 return [self isStateless] ? NO : YES;
737 - (void)setValue:(id)_value forBinding:(NSString *)_name {
740 WODynamicElement *content;
742 ctx = [self context];
743 parent = [ctx parentComponent];
744 content = [ctx componentContent];
747 parent = [self parent];
748 [self warnWithFormat:@"tried to set value of binding '%@' in component "
749 @"'%@' without parent component (parent is '%@') !",
750 _name, [self name], [parent name]];
753 [[self retain] autorelease];
754 [[content retain] autorelease];
756 WOContext_leaveComponent(ctx, self);
757 [[self->wocBindings objectForKey:_name] setValue:_value inComponent:parent];
758 WOContext_enterComponent(ctx, self, content);
760 - (id)valueForBinding:(NSString *)_name {
763 WODynamicElement *content;
766 ctx = [self context];
767 parent = [ctx parentComponent];
768 content = [ctx componentContent];
771 parent = [self parent];
772 [self warnWithFormat:@"tried to retrieve value of binding '%@' in"
773 @" component '%@' without parent component (parent is '%@') !",
774 _name, [self name], [parent name]];
777 [[self retain] autorelease];
778 [[content retain] autorelease];
780 WOContext_leaveComponent(ctx, self);
781 value = [[self->wocBindings objectForKey:_name] valueInComponent:parent];
782 WOContext_enterComponent(ctx, self, content);
787 - (BOOL)hasBinding:(NSString *)_name {
788 return ([self->wocBindings objectForKey:_name] != nil) ? YES : NO;
791 - (BOOL)canSetValueForBinding:(NSString *)_name {
792 WOAssociation *binding;
794 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
797 return [binding isValueSettable];
799 - (BOOL)canGetValueForBinding:(NSString *)_name {
800 WOAssociation *binding;
802 if ((binding = [self->wocBindings objectForKey:_name]) == nil)
808 - (id)performParentAction:(NSString *)_name {
811 WODynamicElement *content;
815 ctx = [self context];
816 parent = [ctx parentComponent];
817 content = [ctx componentContent];
818 action = NSSelectorFromString(_name);
820 if (parent == nil) return nil;
821 if (action == NULL) return nil;
823 NSAssert(parent != self, @"parent component equals current component");
825 if (![parent respondsToSelector:action]) {
826 [self debugWithFormat:@"parent %@ doesn't respond to %@",
827 [parent name], _name];
831 self = [self retain];
833 WOContext_leaveComponent(ctx, self);
834 *(&result) = [parent performSelector:action];
835 WOContext_enterComponent(ctx, self, content);
839 [localException raise];
849 - (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c {
851 [self debugWithFormat:@"%s: default says no.", __PRETTY_FUNCTION__];
855 - (void)takeValuesFromRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
856 WOElement *template = nil;
859 [self debugWithFormat:@"take values from rq 0x%08X", _req];
861 NSAssert1(self->componentFlags.isAwake,
862 @"component %@ is not awake !", self);
864 [self _setContext:_ctx];
865 template = [self _woComponentTemplate];
867 if (template == nil) {
869 [self debugWithFormat:@"cannot take values, component has no template!"];
873 if (template->takeValues) {
874 template->takeValues(template,
875 @selector(takeValuesFromRequest:inContext:),
879 [template takeValuesFromRequest:_req inContext:_ctx];
882 - (id)invokeActionForRequest:(WORequest *)_req inContext:(WOContext *)_ctx {
883 WOElement *template = nil;
886 NSAssert1(self->componentFlags.isAwake, @"component %@ is not awake!", self);
888 [self _setContext:_ctx];
889 template = [self _woComponentTemplate];
890 result = [template invokeActionForRequest:_req inContext:_ctx];
894 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
895 WOElement *template = nil;
896 NSTimeInterval st = 0.0;
898 NSAssert1(self->componentFlags.isAwake,
899 @"component %@ is not awake !", self);
901 if (self->context != _ctx) {
902 [self debugWithFormat:@"%s: component ctx != ctx (%@ vs %@)",
903 __PRETTY_FUNCTION__, self->context, _ctx];
907 [self _setContext:_ctx];
909 if ((template = [self _woComponentTemplate]) == nil) {
911 [self debugWithFormat:@"component has no template (rm=%@).",
912 [self resourceManager]];
918 st = [[NSDateClass date] timeIntervalSince1970];
920 if (template->appendResponse) {
921 template->appendResponse(template,
922 @selector(appendToResponse:inContext:),
926 [template appendToResponse:_response inContext:_ctx];
931 diff = [[NSDateClass date] timeIntervalSince1970] - st;
933 for (i = [_ctx componentStackCount]; i >= 0; i--)
936 [perfLogger logWithFormat:@"Template %@ (comp %@): %0.3fs\n",
943 /* WOActionResults */
945 - (WOResponse *)generateResponse {
946 WOResponse *response = nil;
947 WOContext *ctx = nil;
950 ctx = [self context];
951 ctxID = [ctx contextID];
952 response = [WOResponse responseWithRequest:[ctx request]];
955 [self debugWithFormat:@"missing ctx-id for context %@", ctx];
959 [ctx deleteAllElementIDComponents];
960 [ctx appendElementIDComponent:ctxID];
962 WOContext_enterComponent(ctx, self, nil);
963 [self appendToResponse:response inContext:ctx];
964 WOContext_leaveComponent(ctx, self);
966 [ctx deleteLastElementIDComponent];
971 if ([[[ctx request] method] isEqualToString:@"HEAD"])
972 [response setContent:[NSData data]];
975 /* HTTP/1.1 caching directive, prevents browser from caching dynamic pages */
976 if ([[ctx application] isPageRefreshOnBacktrackEnabled])
977 [response disableClientCaching];
984 - (void)encodeWithCoder:(NSCoder *)_coder {
985 BOOL doesReloadTemplates = self->componentFlags.reloadTemplates;
987 [_coder encodeObject:self->wocBindings];
988 [_coder encodeObject:self->wocName];
989 [_coder encodeConditionalObject:self->parentComponent];
990 [_coder encodeObject:self->subcomponents];
991 [_coder encodeObject:self->wocVariables];
992 [_coder encodeConditionalObject:self->session];
993 [_coder encodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
995 - (id)initWithCoder:(NSCoder *)_decoder {
996 if ((self = [super init])) {
997 BOOL doesReloadTemplates = YES;
999 self->wocBindings = [[_decoder decodeObject] retain];
1000 self->wocName = [[_decoder decodeObject] retain];
1001 self->parentComponent = [_decoder decodeObject]; // non-retained
1002 self->subcomponents = [[_decoder decodeObject] retain];
1003 self->wocVariables = [[_decoder decodeObject] retain];
1004 self->session = [_decoder decodeObject]; // non-retained
1006 [_decoder decodeValueOfObjCType:@encode(BOOL) at:&doesReloadTemplates];
1007 [self setCachingEnabled:!doesReloadTemplates];
1012 /* component variables */
1014 - (BOOL)isStateless {
1018 [self->wocVariables removeAllObjects];
1021 - (void)setObject:(id)_obj forKey:(NSString *)_key {
1022 _setExtraVar(self, _key, _obj);
1024 - (id)objectForKey:(NSString *)_key {
1025 return _getExtraVar(self, _key);
1027 - (NSDictionary *)variableDictionary {
1028 return self->wocVariables;
1031 - (BOOL)logComponentVariableCreations {
1032 /* only if we have a subclass, we can store values in ivars ... */
1033 return (self->isa != WOComponentClass) ? YES : NO;
1036 #if !NG_USE_KVC_FALLBACK /* only override on libFoundation */
1038 - (void)takeValue:(id)_value forKey:(NSString *)_key {
1039 if (WOSetKVCValueUsingMethod(self, _key, _value)) {
1043 if (WOGetKVCGetMethod(self, _key) == NULL) {
1044 if (_value == nil) {
1046 [self debugWithFormat:
1047 @"storing <nil> value in component variable %@", _key];
1050 if ([self->wocVariables objectForKey:_key])
1051 [self setObject:nil forKey:_key];
1056 if ([self logComponentVariableCreations]) {
1057 /* only if we have a subclass, we can store values in ivars ... */
1058 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1059 [self debugWithFormat:
1060 @"Created component variable (class=%@): '%@'.",
1061 NSStringFromClass(self->isa), _key];
1066 [self setObject:_value forKey:_key];
1070 [self debugWithFormat:
1071 @"value %@ could not set via method or KVC "
1072 @"(self responds to %@: %s).",
1074 [self respondsToSelector:NSSelectorFromString(_key)] ? "yes" : "no"];
1079 - (id)valueForKey:(NSString *)_key {
1082 if ((value = WOGetKVCValueUsingMethod(self, _key)))
1086 [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1088 if ((value = [self objectForKey:_key]))
1094 #else /* use fallback methods on other Foundation libraries */
1096 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
1097 // Note: this is not used on libFoundation, insufficient KVC implementation
1099 if (_value == nil) {
1101 [self debugWithFormat:
1102 @"storing <nil> value in component variable %@", _key];
1105 if ([self->wocVariables objectForKey:_key])
1106 [self setObject:nil forKey:_key];
1112 if ([self logComponentVariableCreations]) {
1113 /* only if we have a subclass, we can store values in ivars ... */
1114 if (![[self->wocVariables objectForKey:_key] isNotNull]) {
1115 [self debugWithFormat:@"Created component variable (class=%@): '%@'.",
1116 NSStringFromClass(self->isa), _key];
1121 [self setObject:_value forKey:_key];
1123 - (id)valueForUndefinedKey:(NSString *)_key {
1124 // Note: this is not used on libFoundation, insufficient KVC implementation
1126 [self debugWithFormat:@"KVC: accessed the component variable %@", _key];
1128 return [self objectForKey:_key];
1131 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
1132 // deprecated: pre-Panther method
1133 [self setValue:_value forUndefinedKey:_key];
1135 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1136 // deprecated: pre-Panther method
1137 return [self valueForUndefinedKey:_key];
1140 - (void)unableToSetNilForKey:(NSString *)_key {
1141 // TODO: should we call setValue:NSNull forKey?
1142 [self errorWithFormat:@"unable to set 'nil' for key: '%@'", _key];
1147 - (void)validationFailedWithException:(NSException *)_exception
1148 value:(id)_value keyPath:(NSString *)_keyPath
1150 [self warnWithFormat:
1151 @"formatter failed for value %@ (keyPath=%@): %@",
1152 _value, _keyPath, [_exception reason]];
1157 - (BOOL)isEventLoggingEnabled {
1161 - (BOOL)isDebuggingEnabled {
1164 - (NSString *)loggingPrefix {
1168 if ([n length] == 0)
1169 return @"<component without name>";
1174 /* woo/plist unarchiving */
1176 - (id)unarchiver:(EOKeyValueUnarchiver *)_archiver
1177 objectForReference:(id)_keyPath
1179 return [self valueForKeyPath:_keyPath];
1184 - (id)copyWithZone:(NSZone *)_zone {
1185 // TODO: find out who triggers this
1186 return [self retain];
1191 - (NSString *)description {
1192 NSMutableString *str;
1195 str = [NSMutableString stringWithCapacity:128];
1196 [str appendFormat:@"<0x%08X[%@]: name=%@", self,
1197 NSStringFromClass([self class]), [self name]];
1199 if (self->parentComponent)
1200 [str appendFormat:@" parent=%@", [self->parentComponent name]];
1201 if (self->subcomponents)
1202 [str appendFormat:@" #subs=%i", [self->subcomponents count]];
1204 if (self->componentFlags.isAwake)
1205 [str appendFormat:@" awake=0x%08X", self->context];
1206 else if (self->context == nil)
1207 [str appendString:@" no-ctx"];
1209 if ((tmp = _getExtraVar(self, @"__worm")))
1210 [str appendFormat:@" rm=%@", tmp];
1212 [str appendString:@">"];
1218 - (NSString *)descriptionForResponse:(WOResponse *)_response
1219 inContext:(WOContext *)_context
1224 /* AdvancedBindingAccessors */
1226 - (void)setUnsignedIntValue:(unsigned)_value forBinding:(NSString *)_name {
1227 [self setValue:[NSNumber numberWithUnsignedInt:_value] forBinding:_name];
1229 - (unsigned)unsignedIntValueForBinding:(NSString *)_name {
1230 return [[self valueForBinding:_name] unsignedIntValue];
1233 - (void)setIntValue:(int)_value forBinding:(NSString *)_name {
1234 [self setValue:[NSNumber numberWithInt:_value] forBinding:_name];
1236 - (int)intValueForBinding:(NSString *)_name {
1237 return [[self valueForBinding:_name] intValue];
1240 - (void)setBoolValue:(BOOL)_value forBinding:(NSString *)_name {
1241 [self setValue:[NSNumber numberWithBool:_value] forBinding:_name];
1243 - (BOOL)boolValueForBinding:(NSString *)_name {
1244 return [[self valueForBinding:_name] boolValue];
1247 #if !NG_USE_KVC_FALLBACK
1248 - (id)handleQueryWithUnboundKey:(NSString *)_key {
1249 [self logWithFormat:@"query for unbound key: %@", _key];
1250 return [super handleQueryWithUnboundKey:_key];
1254 @end /* WOComponent */