1/*
2  Copyright (C) 2000-2005 SKYRIX Software AG
3
4  This file is part of SOPE.
5
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
9  later version.
10
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.
15
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
19  02111-1307, USA.
20*/
21
22// ATTENTION: this class is for OGo legacy, so that WO compatibility changes
23//            to WOResourceManager do not break OGo.
24//            So: do not use that class, its DEPRECATED!
25
26#include <NGObjWeb/OWResourceManager.h>
27#include <NGObjWeb/WOComponentDefinition.h>
28#include "WOComponent+private.h"
29#include <NGObjWeb/WORequest.h>
30#include <NGObjWeb/WOApplication.h>
31#include "common.h"
32#import <Foundation/NSNull.h>
33#include "_WOStringTable.h"
34
35/*
36  Component Discovery and Page Creation
37
38    All WO code uses either directly or indirectly the OWResourceManager's
39    -pageWithName:languages: method to instantiate WO components.
40
41    This methods works in three steps:
42
43      1. discovery of files associated with the component
44         - (WOComponentDefinition *)definitionForComponent:(id)_name
45           inFramework:(NSString *)_framework
46           languages:(NSArray *)_languages
47
48      2. creation of a proper WOComponentDefinition, which is some kind
49         of 'blueprint' or 'class' for components
50
51      3. component instantiation using the definition
52
53    All the instantiation/setup work is done by a component definition, the
54    resource manager is only responsible for managing those 'blueprint'
55    resources.
56
57    If you want to customize component creation, you can supply your
58    own WOComponentDefinition in a subclass of OWResourceManager by
59    overriding:
60      - (WOComponentDefinition *)definitionForComponent:(id)_name
61        inFramework:(NSString *)_frameworkName
62        languages:(NSArray *)_languages
63
64  Notably in WO 5.3 the WOResourceManager doesn't seem to handle components
65  anymore.
66*/
67
68/*
69   Note: this was #if !COMPILE_FOR_GSTEP_MAKE - but there is no difference
70         between Xcode and gstep-make?!
71	 The only possible difference might be that .wo wrappers are directly
72	 in the bundle/framework root - but this doesn't relate to Resources.
73
74	 OK, this breaks gstep-make based template lookup which places .wo
75	 wrappers in .woa/Resources/xxx.wo.
76	 This is an issue because .wox are looked up in Contents/Resources
77	 but .wo ones in just Resources.
78
79	 This issue should be fixed in recent woapp-gs.make ...
80*/
81#if COCOA_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
82#  define RSRCDIR_CONTENTS 1
83#endif
84
85@implementation OWResourceManager
86
87static NSFileManager *fm                = nil;
88static Class    UrlClass                = Nil;
89static NSString *resourcePrefix         = @"";
90static NSString *rapidTurnAroundPath    = nil;
91static NSNull   *null                   = nil;
92static BOOL     debugOn                 = NO;
93static BOOL     debugComponentLookup    = NO;
94static BOOL     debugResourceLookup     = NO;
95static BOOL     genMissingResourceLinks = NO;
96
97+ (void)initialize {
98  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
99  static BOOL isInitialized = NO;
100  NSDictionary *defs;
101  if (isInitialized) return;
102  isInitialized = YES;
103
104  fm   = [[NSFileManager defaultManager] retain];
105  null = [[NSNull null] retain];
106  UrlClass = [NSURL class];
107
108  defs = [NSDictionary dictionaryWithObjectsAndKeys:
109                         [NSArray arrayWithObject:@"wo"],
110                         @"WOComponentExtensions",
111                       nil];
112  [ud registerDefaults:defs];
113  debugOn                 = [WOApplication isDebuggingEnabled];
114  debugComponentLookup    = [ud boolForKey:@"WODebugComponentLookup"];
115  debugResourceLookup     = [ud boolForKey:@"WODebugResourceLookup"];
116  genMissingResourceLinks = [ud boolForKey:@"WOGenerateMissingResourceLinks"];
117  rapidTurnAroundPath     = [[ud stringForKey:@"WOProjectDirectory"] copy];
118}
119
120static inline BOOL
121_pathExists(OWResourceManager *self, NSFileManager *fm, NSString *path)
122{
123  BOOL doesExist;
124
125  if (self->existingPathes && (path != nil)) {
126    int i;
127
128    i = (int)(long)NSMapGet(self->existingPathes, path);
129    if (i == 0) {
130      doesExist = [fm fileExistsAtPath:path];
131      NSMapInsert(self->existingPathes, path, (void*)(doesExist ? 1L : 0xFFL));
132    }
133    else
134      doesExist = i == 1 ? YES : NO;
135  }
136  else
137    doesExist = [fm fileExistsAtPath:path];
138  return doesExist;
139}
140
141+ (void)setResourcePrefix:(NSString *)_prefix {
142  [resourcePrefix autorelease];
143  resourcePrefix = [_prefix copy];
144}
145
146- (id)initWithPath:(NSString *)_path {
147  if ((self = [super init])) {
148    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
149    NSString *rprefix = nil;
150    NSString *tmp;
151
152    self->componentDefinitions =
153      NSCreateMapTable(NSObjectMapKeyCallBacks,
154                       NSObjectMapValueCallBacks,
155                       128);
156    self->stringTables =
157      NSCreateMapTable(NSObjectMapKeyCallBacks,
158                       NSObjectMapValueCallBacks,
159                       16);
160
161    tmp = [_path stringByStandardizingPath];
162    if (tmp) _path = tmp;
163
164    self->base = [_path copy];
165
166    if ([WOApplication isCachingEnabled]) {
167      self->existingPathes = NSCreateMapTable(NSObjectMapKeyCallBacks,
168                                              NSIntMapValueCallBacks,
169                                              256);
170    }
171
172    rprefix = [ud stringForKey:@"WOResourcePrefix"];
173    if (rprefix) [[self class] setResourcePrefix:rprefix];
174  }
175  return self;
176}
177- (id)init {
178  return [self initWithPath:[[NGBundle mainBundle] bundlePath]];
179}
180
181- (void)dealloc {
182  if (self->existingPathes)       NSFreeMapTable(self->existingPathes);
183  if (self->stringTables)         NSFreeMapTable(self->stringTables);
184  if (self->componentDefinitions) NSFreeMapTable(self->componentDefinitions);
185  if (self->keyedResources)       NSFreeMapTable(self->keyedResources);
186  [self->w3resources release];
187  [self->resources   release];
188  [self->base        release];
189  [super dealloc];
190}
191
192/* debugging */
193
194- (BOOL)isDebuggingEnabled {
195  return debugOn;
196}
197
198/* path methods */
199
200- (NSFileManager *)fileManager {
201  static NSFileManager *fm = nil;
202  if (fm == nil)
203    fm = [[NSFileManager defaultManager] retain];
204  return fm;
205}
206
207- (NSString *)basePath {
208  return self->base;
209}
210
211- (NSString *)resourcesPath {
212  NSFileManager *fm;
213
214  if (self->resources)
215    return self->resources;
216
217  fm = [self fileManager];
218  if ([self->base length] > 0) {
219    if (![fm fileExistsAtPath:self->base]) {
220      [self warnWithFormat:@"(%s): Resources base path '%@' does not exist !",
221              __PRETTY_FUNCTION__, self->base];
222      return nil;
223    }
224  }
225
226#if RSRCDIR_CONTENTS
227  if ([rapidTurnAroundPath length] > 0) {
228    /*
229      In rapid turnaround mode, first check for a Resources subdir in the
230      project directory, then directly in the project dir.
231      Note: you cannot have both! Either put stuff in a Resources subdir *or*
232            in the project dir.
233    */
234    NSString *tmp;
235    BOOL isDir;
236
237    tmp = [rapidTurnAroundPath stringByAppendingPathComponent:@"Resources"];
238    if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
239      isDir = NO;
240    if (!isDir)
241      tmp = rapidTurnAroundPath;
242
243    self->resources = [tmp copy];
244  }
245  else {
246    self->resources =
247      [[[self->base stringByAppendingPathComponent:@"Contents"]
248                    stringByAppendingPathComponent:@"Resources"]
249                    copy];
250  }
251#else
252  self->resources =
253    [[self->base stringByAppendingPathComponent:@"Resources"] copy];
254#endif
255
256  if ([self->resources length] > 0) {
257    if (![fm fileExistsAtPath:self->resources]) {
258      [self warnWithFormat:
259              @"(%s): Resources path %@ does not exist !",
260              __PRETTY_FUNCTION__, self->resources];
261      [self->resources release]; self->resources = nil;
262    }
263    else if (self->existingPathes && (self->resources != nil))
264      NSMapInsert(self->existingPathes, self->resources, (void*)1);
265  }
266  return self->resources;
267}
268
269- (NSString *)resourcesPathForFramework:(NSString *)_fw {
270  if (_fw == nil)
271    return [self resourcesPath];
272
273#if RSRCDIR_CONTENTS
274  return [[_fw stringByAppendingPathComponent:@"Contents"]
275               stringByAppendingPathComponent:@"Resources"];
276#else
277  return [_fw stringByAppendingPathComponent:@"Resources"];
278#endif
279}
280
281- (NSString *)webServerResourcesPath {
282  NSFileManager *fm;
283
284  if (self->w3resources)
285    return self->w3resources;
286
287#if GNUSTEP_BASE_LIBRARY && 0
288  self->w3resources =
289    [[self->base stringByAppendingPathComponent:@"Resources/WebServer"] copy];
290#else
291  self->w3resources =
292    [[self->base stringByAppendingPathComponent:@"WebServerResources"] copy];
293#endif
294
295  fm = [self fileManager];
296  if ([self->w3resources length] == 0)
297    return nil;
298
299  if (![fm fileExistsAtPath:self->w3resources]) {
300    static BOOL didLog = NO;
301    if (!didLog) {
302      didLog = YES;
303      [self warnWithFormat:
304              @"(%s): WebServerResources path '%@' does not exist !",
305              __PRETTY_FUNCTION__, self->w3resources];
306    }
307    [self->w3resources release]; self->w3resources = nil;
308  }
309  else if (self->existingPathes && (self->w3resources != nil))
310    NSMapInsert(self->existingPathes, self->w3resources, (void*)1);
311
312  if (debugResourceLookup)
313    [self logWithFormat:@"WebServerResources: '%@'", self->w3resources];
314  return self->w3resources;
315}
316
317- (NSString *)_lookupResourceNamed:(NSString *)_name inPath:(NSString *)_path
318  inFramework:(NSString *)_frameworkName
319  languages:(NSArray *)_languages
320{
321  unsigned i, langCount;
322  NSString *resource;
323
324  if (_path == nil)
325    return nil;
326
327  // first check Language.lproj in WebServerResources
328  for (i = 0, langCount = [_languages count]; i < langCount; i++) {
329    NSString *langPath;
330
331    langPath = [_languages objectAtIndex:i];
332    langPath = [langPath stringByAppendingPathExtension:@"lproj"];
333    langPath = [_path stringByAppendingPathComponent:langPath];
334
335    if (!_pathExists(self, fm, langPath)) {
336      if (debugResourceLookup) {
337	[self logWithFormat:
338		@"  no language lproj for '%@' in path: %@",
339	        [_languages objectAtIndex:i], _path];
340      }
341      continue;
342    }
343
344    resource = [langPath stringByAppendingPathComponent:_name];
345
346    if (_pathExists(self, fm, resource)) {
347      if (debugResourceLookup)
348	[self logWithFormat:@"  found path: %@", resource];
349      return resource;
350    }
351    else if (debugResourceLookup)
352      [self logWithFormat:@"  not found in path: %@", resource];
353  }
354
355  /* next check in resources path (WebServerResources or Resources) itself */
356  resource = [_path stringByAppendingPathComponent:_name];
357  if (debugResourceLookup)
358    [self logWithFormat:@"  check for flat path: %@", resource];
359  if (_pathExists(self, fm, resource))
360    return resource;
361
362  return nil;
363}
364
365- (BOOL)shouldLookupResourceInWebServerResources:(NSString *)_name {
366  if ([_name hasSuffix:@".wox"]) return NO;
367  if ([_name hasSuffix:@".wo"])  return NO;
368  return YES;
369}
370
371- (NSString *)pathForResourceNamed:(NSString *)_name
372  inFramework:(NSString *)_frameworkName
373  languages:(NSArray *)_languages
374{
375  /*
376    Note: at least in the case of OGo component lookups the framework name is
377          properly filled with the OGo bundle path on lookup, so no
378          NGBundleManager query is necessary.
379  */
380  NSFileManager *fm;
381  NSString      *resource = nil;
382
383  if (debugResourceLookup) {
384    [self logWithFormat:@"lookup '%@' bundle=%@ languages=%@",
385          _name, _frameworkName, [_languages componentsJoinedByString:@","]];
386  }
387
388  fm        = [self fileManager];
389
390  /* now check in webserver resources path */
391
392  if ([self shouldLookupResourceInWebServerResources:_name]) {
393    resource = [self _lookupResourceNamed:_name
394		     inPath:[self webServerResourcesPath]
395		     inFramework:_frameworkName languages:_languages];
396    if (resource != nil) return resource;
397  }
398
399  /* now check in regular resources path */
400
401  resource = [self _lookupResourceNamed:_name
402		   inPath:[self resourcesPathForFramework:_frameworkName]
403		   inFramework:_frameworkName languages:_languages];
404  if (resource != nil) return resource;
405
406  /* and last check in the application directory */
407  if (_pathExists(self, fm, self->base)) {
408    resource = [self->base stringByAppendingPathComponent:_name];
409    if (_pathExists(self, fm, resource))
410      return resource;
411  }
412  return nil;
413}
414
415- (NSString *)pathForResourceNamed:(NSString *)_name {
416  IS_DEPRECATED;
417  return [self pathForResourceNamed:_name inFramework:nil languages:nil];
418}
419
420- (NSString *)pathForResourceNamed:(NSString *)_name ofType:(NSString *)_type {
421  _name = [_name stringByAppendingPathExtension:_type];
422  return [self pathForResourceNamed:_name];
423}
424
425/* URL methods */
426
427- (NSString *)urlForResourceNamed:(NSString *)_name
428  inFramework:(NSString *)_frameworkName
429  languages:(NSArray *)_languages
430  request:(WORequest *)_request
431{
432  WOApplication *app;
433  NSString *resource = nil, *tmp;
434
435  app = [WOApplication application];
436
437  if (_languages == nil)
438    _languages = [_request browserLanguages];
439
440  resource = [self pathForResourceNamed:_name
441                   inFramework:_frameworkName
442                   languages:_languages];
443#if RSRCDIR_CONTENTS
444  if ([resource rangeOfString:@"/Contents/"].length > 0) {
445    resource = [resource stringByReplacingString:@"/Contents"
446                         withString:@""];
447  }
448#endif
449#if 0
450  tmp = [resource stringByStandardizingPath];
451  if (tmp) resource = tmp;
452#endif
453
454  if (resource) {
455    NSString *path = nil, *sbase;
456    unsigned len;
457
458    sbase = self->base;
459    tmp  = [sbase commonPrefixWithString:resource options:0];
460
461    len  = [tmp length];
462    path = [sbase    substringFromIndex:len];
463    tmp  = [resource substringFromIndex:len];
464    if (([path length] > 0) && ![tmp hasPrefix:@"/"] && ![tmp hasPrefix:@"\\"])
465      path = [path stringByAppendingString:@"/"];
466    path = [path stringByAppendingString:tmp];
467
468#ifdef __WIN32__
469    {
470      NSArray *cs;
471      cs   = [path componentsSeparatedByString:@"\\"];
472      path = [cs componentsJoinedByString:@"/"];
473    }
474#endif
475
476    if (path) {
477      static NSString *suffix = nil;
478      NSMutableString *url = nil;
479
480      if (suffix == nil) {
481	NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
482        suffix = [ud stringForKey:@"WOApplicationSuffix"];
483      }
484
485      url = [[NSMutableString alloc] initWithCapacity:256];
486#if 0
487      [url appendString:[_request adaptorPrefix]];
488#endif
489      if (resourcePrefix)
490        [url appendString:resourcePrefix];
491      if (![url hasSuffix:@"/"]) [url appendString:@"/"];
492      [url appendString:app ? [app name] : [_request applicationName]];
493      [url appendString:suffix];
494      if (![path hasPrefix:@"/"]) [url appendString:@"/"];
495      [url appendString:path];
496
497      path = [url copy];
498      [url release];
499
500      return [path autorelease];
501    }
502  }
503
504  if (genMissingResourceLinks) {
505    return [NSString stringWithFormat:
506                       @"/missingresource?name=%@&application=%@",
507                       _name, app ? [app name] : [_request applicationName]];
508  }
509  return nil;
510}
511
512- (NSString *)urlForResourceNamed:(NSString *)_name {
513  IS_DEPRECATED;
514  return [self urlForResourceNamed:_name
515               inFramework:nil
516               languages:nil
517               request:nil];
518}
519- (NSString *)urlForResourceNamed:(NSString *)_name ofType:(NSString *)_type {
520  return [self urlForResourceNamed:
521                 [_name stringByAppendingPathExtension:_type]];
522}
523
524/* string tables */
525
526- (NSString *)stringForKey:(NSString *)_key
527  inTableNamed:(NSString *)_tableName
528  withDefaultValue:(NSString *)_defaultValue
529  inFramework:(NSString *)_framework
530  languages:(NSArray *)_languages;
531{
532  NSFileManager  *fm;
533  _WOStringTable *table     = nil;
534  NSString       *path      = nil;
535
536  fm = [self fileManager];
537
538  if (_tableName == nil)
539    _tableName = @"Localizable";
540
541  /* take a look whether a matching table is already loaded */
542
543  path = [_tableName stringByAppendingPathExtension:@"strings"];
544  path = [self pathForResourceNamed:path inFramework:_framework
545               languages:_languages];
546
547  if (path != nil) {
548    if ((table = NSMapGet(self->stringTables, path)) == NULL) {
549      if ([fm fileExistsAtPath:path]) {
550	table = [_WOStringTable allocWithZone:[self zone]]; /* for gcc */
551        table = [table initWithPath:path];
552        NSMapInsert(self->stringTables, path, table);
553        [table release];
554      }
555    }
556    if (table != nil)
557      return [table stringForKey:_key withDefaultValue:_defaultValue];
558  }
559  /* didn't found table in cache */
560
561  return _defaultValue;
562}
563
564- (NSString *)stringForKey:(NSString *)_key
565  inTableNamed:(NSString *)_tableName
566  withDefaultValue:(NSString *)_default
567  languages:(NSArray *)_languages
568{
569  return [self stringForKey:_key inTableNamed:_tableName
570               withDefaultValue:_default
571               inFramework:nil
572               languages:_languages];
573}
574
575
576/* NSLocking */
577
578- (void)lock {
579}
580- (void)unlock {
581}
582
583/* component definitions */
584
585- (NSString *)pathToComponentNamed:(NSString *)_name
586  inFramework:(NSString *)_framework
587  languages:(NSArray *)_langs
588{
589  /* search for component wrapper .. */
590  // TODO: shouldn't we use that for WOx as well?
591  NSEnumerator *e;
592  NSString     *ext;
593
594  if (_name == nil) {
595#if DEBUG
596    [self warnWithFormat:@"(%s): tried to get path to component with "
597            @"<nil> name !",
598            __PRETTY_FUNCTION__];
599#endif
600    return nil;
601  }
602
603  /* scan for name.$ext resource ... */
604  e = [[[NSUserDefaults standardUserDefaults]
605                        arrayForKey:@"WOComponentExtensions"]
606                        objectEnumerator];
607
608  while ((ext = [e nextObject])) {
609    NSString *specName;
610    NSString *path;
611
612    specName = [_name stringByAppendingPathExtension:ext];
613
614    path = [self pathForResourceNamed:specName
615                 inFramework:_framework
616                 languages:_langs];
617    if (path != nil) return path;
618  }
619  return nil;
620}
621
622- (NSString *)pathToComponentNamed:(NSString *)_name
623  inFramework:(NSString *)_fw
624{
625  // TODO: is this still used somewhere?
626  return [self pathToComponentNamed:_name inFramework:_fw languages:nil];
627}
628
629- (WOComponentDefinition *)_definitionForPathlessComponent:(NSString *)_name
630  languages:(NSArray *)_languages
631{
632  /* definition factory */
633  WOComponentDefinition *cdef;
634
635  cdef = [[WOComponentDefinition allocWithZone:[self zone]]
636                                 initWithName:_name
637                                 path:nil
638                                 baseURL:nil
639                                 frameworkName:nil];
640
641  return [cdef autorelease];
642}
643
644- (WOComponentDefinition *)_definitionWithName:(NSString *)_name
645  url:(NSURL *)_url
646  baseURL:(NSURL *)_baseURL
647  frameworkName:(NSString *)_fwname
648{
649  /* definition factory */
650  static Class DefClass;
651  id cdef;
652
653  if (DefClass == Nil)
654    DefClass = [WOComponentDefinition class];
655
656  cdef = [[DefClass alloc] initWithName:_name
657                           path:[_url path]
658                           baseURL:_baseURL frameworkName:_fwname];
659  return cdef;
660}
661- (WOComponentDefinition *)_definitionWithName:(NSString *)_name
662  path:(NSString *)_path
663  baseURL:(NSURL *)_baseURL
664  frameworkName:(NSString *)_fwname
665{
666  NSURL *url;
667
668  url = ([_path length] > 0)
669    ? [[[NSURL alloc] initFileURLWithPath:_path] autorelease]
670    : nil;
671
672  return [self _definitionWithName:_name url:url
673               baseURL:_baseURL frameworkName:_fwname];
674}
675
676- (WOComponentDefinition *)_cachedDefinitionForComponent:(id)_name
677  languages:(NSArray *)_languages
678{
679  NSArray *cacheKey;
680  id      cdef;
681
682  if (self->componentDefinitions == NULL)
683    return nil;
684  if (![[WOApplication application] isCachingEnabled])
685    return nil;
686
687  cacheKey = [NSArray arrayWithObjects:_name, _languages, nil];
688  cdef     = NSMapGet(self->componentDefinitions, cacheKey);
689
690  return cdef;
691}
692- (WOComponentDefinition *)_cacheDefinition:(id)_cdef
693  forComponent:(id)_name
694  languages:(NSArray *)_languages
695{
696  NSArray *cacheKey;
697
698  if (self->componentDefinitions == NULL)
699    return _cdef;
700  if (![[WOApplication application] isCachingEnabled])
701    return _cdef;
702
703  cacheKey = [NSArray arrayWithObjects:_name, _languages, nil];
704  NSMapInsert(self->componentDefinitions, cacheKey, _cdef ? _cdef : (id)null);
705
706  return _cdef;
707}
708
709- (NSString *)resourceNameForComponentNamed:(NSString *)_name {
710  return [_name stringByAppendingPathExtension:@"wox"];
711}
712
713- (BOOL)_isValidWrapperDirectory:(NSString *)_path
714  containingTemplate:(NSString *)_name
715{
716  /*
717     Check whether this actually does contain a template!
718
719     This is new and hopefully doesn't break anything, but as far as I can
720     see checking for Component.html inside should do the right thing (unless
721     there are template wrappers which are not .wo wrappers ;-)
722  */
723  NSString *htmlPath;
724
725  htmlPath = [_name stringByAppendingPathExtension:@"html"];
726  htmlPath = [_path stringByAppendingPathComponent:htmlPath];
727  return [[self fileManager] fileExistsAtPath:htmlPath];
728}
729
730- (WOComponentDefinition *)_processWrapperLanguageProjects:(NSString *)_name
731  componentPath:(NSString *)componentPath
732  languages:(NSArray *)_langs
733{
734  /*
735     this looks for language projects contained in template wrapper
736     directories, eg "Main.wo/English.lproj/"
737  */
738  WOComponentDefinition *cdef = nil;
739  NSFileManager         *fm   = nil;
740  NSEnumerator *languages;
741  NSString     *language;
742  NSString     *sname;
743  BOOL         doesCache;
744
745  if ([_langs count] == 0)
746    return nil;
747
748  doesCache = [[WOApplication application] isCachingEnabled];
749  fm        = [self fileManager];
750  sname     = [_name stringByAppendingString:@"\t"];
751
752  languages = [_langs objectEnumerator];
753  while ((language = [languages nextObject])) {
754    NSString *compoundKey  = nil;
755    NSString *languagePath = nil;
756    BOOL     isDirectory   = NO;
757    NSString *baseUrl = nil;
758
759    // [self logWithFormat:@"check %@ / %@", _name, language];
760
761    compoundKey = [sname stringByAppendingString:language];
762    if (doesCache) {
763      cdef = NSMapGet(self->componentDefinitions, compoundKey);
764
765      if (cdef == (id)null)
766	/* resource does not exist */
767	continue;
768
769      [cdef touch];
770      if (cdef) return cdef; // found definition in cache
771    }
772
773    /* take a look into the file system */
774    languagePath = [language stringByAppendingPathExtension:@"lproj"];
775    languagePath = [componentPath stringByAppendingPathComponent:languagePath];
776
777    if (![fm fileExistsAtPath:languagePath isDirectory:&isDirectory]) {
778      if (doesCache) {
779	// register null in cache, so that we know it's non-existent
780	NSMapInsert(self->componentDefinitions, compoundKey, null);
781      }
782      continue;
783    }
784
785    if (!isDirectory) {
786      [self warnWithFormat:@"(%s): language entry %@ is not a directory !",
787              __PRETTY_FUNCTION__, languagePath];
788      if (doesCache && (compoundKey != nil)) {
789	// register null in cache, so that we know it's non-existent
790	NSMapInsert(self->componentDefinitions, compoundKey, null);
791      }
792      continue;
793    }
794
795    /*
796       Now check whether this actually does contain a template!
797
798       This is new and hopefully doesn't break anything, but as far as I can
799       see checking for Component.html inside should do the right thing (unless
800       there are template wrappers which are not .wo wrappers ;-)
801    */
802    if (![self _isValidWrapperDirectory:languagePath
803	       containingTemplate:_name]){
804      [self debugWithFormat:@"no HTML template for inside lproj '%@': '%@'",
805	      _name, languagePath];
806      if (doesCache && (compoundKey != nil)) {
807	// register null in cache, so that we know it's non-existent
808	NSMapInsert(self->componentDefinitions, compoundKey, null);
809      }
810      continue;
811    }
812
813    /* construct the base URL */
814
815    baseUrl = [[[WOApplication application] baseURL] absoluteString];
816    baseUrl = [NSString stringWithFormat:@"%@/%@.lproj/%@.wo",
817			  baseUrl, language, _name];
818
819    /* create WOComponentDefinition object */
820
821    cdef = [self _definitionWithName:_name
822		 path:languagePath
823		 baseURL:[NSURL URLWithString:baseUrl]
824		 frameworkName:nil];
825    if (cdef == nil) {
826      [self warnWithFormat:@"(%s): could not load component definition of "
827              @"'%@' from language project: %@",
828              __PRETTY_FUNCTION__, _name, languagePath];
829      if (doesCache && (compoundKey != nil)) {
830	// register null in cache, so that we know it's non-existent
831	NSMapInsert(self->componentDefinitions, compoundKey, null);
832      }
833      continue;
834    }
835
836    if (doesCache && (compoundKey != nil)) {
837      // register in cache
838      NSMapInsert(self->componentDefinitions, compoundKey, cdef);
839      [cdef release];
840    }
841    else {
842      // don't register in cache
843      cdef = [cdef autorelease];
844    }
845
846    return cdef;
847  }
848
849  return nil; /* no lproj containing templates was found */
850}
851
852- (NSString *)defaultFrameworkForComponentNamed:(NSString *)_name {
853  Class clazz;
854
855  if (rapidTurnAroundPath != nil)
856    return rapidTurnAroundPath;
857
858  if ((clazz = NSClassFromString(_name)) == nil)
859    return nil;
860
861  return [[NSBundle bundleForClass:clazz] bundlePath];
862}
863
864- (WOComponentDefinition *)definitionForComponent:(id)_name
865  inFramework:(NSString *)_framework
866  languages:(NSArray *)_languages
867{
868  /* this is the primary method for finding a definition */
869  // TODO: this method is too large
870  WOApplication         *app;
871  NSFileManager         *fm            = nil;
872  WOComponentDefinition *cdef          = nil;
873  NSURL                 *componentURL;
874  BOOL                  doesCache, isDir;
875
876  app       = [WOApplication application];
877  doesCache = [app isCachingEnabled];
878
879  /* lookup component path */
880
881  if ([_name isKindOfClass:UrlClass]) {
882    componentURL = _name;
883    _name = [componentURL path];
884    if (debugComponentLookup) {
885      [self debugWithFormat:@"using URL %@ for component %@",
886              componentURL, _name];
887    }
888  }
889  else {
890    NSString *path;
891
892    /*
893       Note: this is a bit of a hack ..., actually this method should never
894             be called without a framework and pages shouldn't be instantiated
895             without specifying their framework.
896             But for legacy reasons this needs to be done and seems to work
897             without problems. It is required for loading components from
898             bundles.
899    */
900    if (_framework == nil && _name != nil)
901      _framework = [self defaultFrameworkForComponentNamed:_name];
902
903    if (debugComponentLookup) {
904      [self logWithFormat:@"lookup: component '%@' in framework '%@'",
905              _name, _framework];
906    }
907
908    /* look for .wox component */
909
910    // TODO: why don't we use -pathForComponentNamed: here?
911    path = [self pathForResourceNamed:
912                   [self resourceNameForComponentNamed:_name]
913                 inFramework:_framework
914                 languages:_languages];
915
916    if (debugComponentLookup)
917      [self logWithFormat:@"lookup:  path-to-resource: '%@'", path];
918
919    /* look for .wo component */
920
921    if ([path length] == 0) {
922      path = [self pathToComponentNamed:_name
923                   inFramework:_framework
924                   languages:_languages];
925      if (debugComponentLookup)
926        [self logWithFormat:@"lookup:  path-to-component: '%@'", path];
927    }
928
929    /* make URL from path */
930
931    componentURL = ([path length] > 0)
932      ? [[[UrlClass alloc] initFileURLWithPath:path] autorelease]
933      : nil;
934  }
935
936  if (debugComponentLookup) {
937    [self logWithFormat:@"  component='%@' in framework='%@'",
938            _name, _framework];
939    [self logWithFormat:@"  => '%@'", [componentURL absoluteString]];
940  }
941
942  /* check whether it's a 'template-less' component ... */
943
944  if (componentURL == nil) {
945    /* did not find component wrapper ! */
946    [app debugWithFormat:@"  component '%@' has no template !", _name];
947
948    cdef = [self _definitionForPathlessComponent:_name languages:_languages];
949    return cdef;
950  }
951
952  fm = [self fileManager];
953
954  /* ensure that the component exists */
955
956  isDir = NO;
957  if ([componentURL isFileURL]) {
958    NSString *componentPath;
959
960    componentPath = [componentURL path];
961
962    if (![fm fileExistsAtPath:componentPath isDirectory:&isDir]) {
963      [[WOApplication application]
964                      debugWithFormat:
965                        @"%s: did not find component '%@' at path '%@' !",
966                        __PRETTY_FUNCTION__,
967                        _name, componentPath];
968      return nil;
969    }
970
971    /* if the component spec is a directory (eg a .wo), scan lproj's inside */
972    if (isDir && [_languages count] > 0) {
973      if (debugComponentLookup) {
974	[self logWithFormat:@"  check wrapper languages (%d)",
975	      [_languages count]];
976      }
977      cdef = [self _processWrapperLanguageProjects:_name
978		   componentPath:componentPath
979		   languages:_languages];
980      if (cdef != nil) {
981	if (debugComponentLookup)
982	  [self logWithFormat:@"  => FOUND: %@", cdef];
983	return cdef;
984      }
985      else if (debugComponentLookup)
986	[self logWithFormat:@"  ... no language template found ..."];
987    }
988  }
989
990  /* look flat */
991
992  if (doesCache) {
993    cdef = NSMapGet(self->componentDefinitions, componentURL);
994    if (cdef == (id)null)
995      /* resource does not exist */
996      return nil;
997    [cdef touch];
998
999    if (cdef != nil) return cdef; // found definition in cache
1000  }
1001
1002  /*
1003     in case the "componentURL" is a directory, check whether it contains
1004     an HTML file
1005  */
1006  if (isDir) {
1007    if (![self _isValidWrapperDirectory:[componentURL path]
1008	       containingTemplate:_name]) {
1009      if (debugComponentLookup)
1010	[self logWithFormat:@"  not a valid wrapper '%@': '%@'",
1011	        _name, [componentURL absoluteString]];
1012      if (doesCache) {
1013        /* register null in cache, so that we know it's non-existent */
1014        NSMapInsert(self->componentDefinitions, componentURL, null);
1015      }
1016      return nil;
1017    }
1018  }
1019
1020  /* take a look into the file system */
1021  {
1022    NSString *baseUrl = nil;
1023
1024    baseUrl = [NSString stringWithFormat:@"%@/%@",
1025                          [[app baseURL] absoluteString],
1026			  [_name lastPathComponent]];
1027
1028    cdef = [self _definitionWithName:_name
1029                 url:componentURL
1030                 baseURL:[NSURL URLWithString:baseUrl]
1031                 frameworkName:nil];
1032    if (cdef == nil) {
1033      [self warnWithFormat:@"(%s): could not load component definition of "
1034              @"'%@' from component wrapper: '%@'",
1035              __PRETTY_FUNCTION__, _name, componentURL];
1036      if (doesCache) {
1037        /* register null in cache, so that we know it's non-existent */
1038        NSMapInsert(self->componentDefinitions, componentURL, null);
1039      }
1040      return nil;
1041    }
1042
1043    if (doesCache) {
1044      /* register in cache */
1045      NSMapInsert(self->componentDefinitions, componentURL, cdef);
1046      [cdef release];
1047    }
1048    else
1049      /* don't register in cache, does not cache */
1050      cdef = [cdef autorelease];
1051
1052    return cdef;
1053  }
1054
1055  /* did not find component */
1056  return nil;
1057}
1058- (WOComponentDefinition *)definitionForComponent:(id)_name
1059  languages:(NSArray *)_langs
1060{
1061  /* Note: the framework will be determined base on the class '_name' */
1062  return [self definitionForComponent:_name inFramework:nil languages:_langs];
1063}
1064
1065/* caching */
1066
1067- (WOComponentDefinition *)__definitionForComponent:(id)_name
1068  languages:(NSArray *)_languages
1069{
1070  // TODO: this should add the framework parameter and maybe a context
1071  WOComponentDefinition *cdef;
1072
1073  /* look into cache */
1074
1075  cdef = [self _cachedDefinitionForComponent:_name languages:_languages];
1076  if (cdef != nil) {
1077    if (cdef == (id)null)
1078      /* component does not exist */
1079      return nil;
1080
1081    if ([cdef respondsToSelector:@selector(touch)])
1082      [cdef touch];
1083    return cdef;
1084  }
1085
1086  /* not cached, create a definition */
1087
1088  cdef = [self definitionForComponent:_name languages:_languages];
1089
1090  /* cache created definition */
1091
1092  return [self _cacheDefinition:cdef forComponent:_name languages:_languages];
1093}
1094
1095/* primary call-in's */
1096
1097- (WOElement *)templateWithName:(NSString *)_name
1098  languages:(NSArray *)_languages
1099{
1100  WOComponentDefinition *cdef;
1101
1102  cdef = [self __definitionForComponent:_name languages:_languages];
1103  if (cdef == nil) return nil;
1104
1105  return (WOElement *)[cdef template];
1106}
1107
1108- (id)pageWithName:(NSString *)_name languages:(NSArray *)_languages {
1109  /*
1110     TODO: this appears to be deprecated since the WOComponent initializer
1111           is now -initWithContext: and we have no context here ...
1112           Also misses the framework?
1113  */
1114  NSAutoreleasePool     *pool      = nil;
1115  WOComponentDefinition *cdef      = nil;
1116  WOComponent           *component = nil;
1117
1118  pool = [[NSAutoreleasePool alloc] init];
1119  {
1120    cdef = [self __definitionForComponent:_name languages:_languages];
1121    if (cdef != nil) {
1122      component =
1123	[cdef instantiateWithResourceManager:(WOResourceManager *)self
1124	      languages:_languages];
1125      component = [component retain];
1126    }
1127  }
1128  [pool release];
1129
1130  return [component autorelease];
1131}
1132
1133/* KeyedData */
1134
1135- (void)setData:(NSData *)_data
1136  forKey:(NSString *)_key
1137  mimeType:(NSString *)_type
1138  session:(WOSession *)_session
1139{
1140  if ((_key == nil) || (_data == nil))
1141    return;
1142  if (_type == nil)
1143    _type = @"application/octet-stream";
1144
1145  [self lock];
1146
1147  if (self->keyedResources == NULL) {
1148    self->keyedResources = NSCreateMapTable(NSObjectMapKeyCallBacks,
1149                                            NSObjectMapValueCallBacks,
1150                                            128);
1151  }
1152
1153  NSMapInsert(self->keyedResources,
1154              _key,
1155              [NSDictionary dictionaryWithObjectsAndKeys:
1156                              _type, @"mimeType",
1157                              _key,  @"key",
1158                              _data, @"data",
1159                            nil]);
1160
1161  [self unlock];
1162}
1163
1164- (id)_dataForKey:(NSString *)_key sessionID:(NSString *)_sid {
1165  id tmp;
1166
1167  [self lock];
1168
1169  if (self->keyedResources)
1170    tmp = NSMapGet(self->keyedResources, _key);
1171  else
1172    tmp = nil;
1173
1174  tmp = [[tmp retain] autorelease];
1175
1176  [self unlock];
1177
1178  return tmp;
1179}
1180
1181- (void)removeDataForKey:(NSString *)_key session:(WOSession *)_session {
1182  [self lock];
1183
1184  if (self->keyedResources)
1185    NSMapRemove(self->keyedResources, _key);
1186
1187  [self unlock];
1188}
1189
1190- (void)flushDataCache {
1191  [self lock];
1192
1193  if (self->keyedResources) {
1194    NSFreeMapTable(self->keyedResources);
1195    self->keyedResources = NULL;
1196  }
1197
1198  [self unlock];
1199}
1200
1201/* description */
1202
1203- (NSString *)description {
1204  return [NSString stringWithFormat:@"<%@[0x%p]: path=%@>",
1205                     [self class], self, self->base];
1206
1207}
1208
1209@end /* OWResourceManager */
1210