1/*
2  Copyright (C) 2000-2006 SKYRIX Software AG
3  Copyright (C) 2006      Helge Hess
4
5  This file is part of SOPE.
6
7  SOPE is free software; you can redistribute it and/or modify it under
8  the terms of the GNU Lesser General Public License as published by the
9  Free Software Foundation; either version 2, or (at your option) any
10  later version.
11
12  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
13  WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15  License for more details.
16
17  You should have received a copy of the GNU Lesser General Public
18  License along with SOPE; see the file COPYING.  If not, write to the
19  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20  02111-1307, USA.
21*/
22
23#include "NGBundleManager.h"
24#include "common.h"
25#include <NGExtensions/NSObject+Logs.h>
26#include <NGExtensions/NSNull+misc.h>
27#import <Foundation/NSFileManager.h>
28#import <EOControl/EOQualifier.h>
29#include <ctype.h>
30
31#if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
32#  include <NGExtensions/NGPropertyListParser.h>
33#endif
34
35#if LIB_FOUNDATION_LIBRARY
36@interface NSBundle(UsedPrivates)
37+ (BOOL)isFlattenedDirLayout;
38@end
39#endif
40
41#if NeXT_RUNTIME || APPLE_RUNTIME
42
43#include <objc/objc-runtime.h>
44
45//OBJC_EXPORT void objc_setClassHandler(int (*)(const char *));
46
47static BOOL debugClassHook = NO;
48static BOOL hookDoLookup   = YES;
49
50static int _getClassHook(const char *className) {
51  // Cocoa variant
52  if (className == NULL) return 0;
53
54  if (debugClassHook)
55    printf("lookup class '%s'.\n", className);
56
57  if (objc_lookUpClass(className))
58    return 1;
59
60  if (hookDoLookup) {
61    static NGBundleManager *manager = nil;
62    NSBundle *bundle;
63    NSString *cns;
64
65    if (debugClassHook)
66      printf("%s: look for class %s\n", __PRETTY_FUNCTION__, className);
67    if (manager == nil)
68      manager = [NGBundleManager defaultBundleManager];
69
70    cns = [[NSString alloc] initWithCString:className];
71    bundle = [manager bundleForClassNamed:cns];
72    [cns release]; cns = nil;
73
74    if (bundle != nil) {
75      if (debugClassHook) {
76	NSLog(@"%s: found bundle %@", __PRETTY_FUNCTION__,
77	      [bundle bundlePath]);
78      }
79
80      if (![manager loadBundle:bundle]) {
81	fprintf(stderr,
82		"bundleManager couldn't load bundle for class '%s'.\n",
83                className);
84      }
85#if 0
86      else {
87        Class c = objc_lookUpClass(className);
88        NSLog(@"%s: loaded bundle %@ for className %s class %@",
89	      __PRETTY_FUNCTION__,
90              bundle, className, c);
91      }
92#endif
93    }
94  }
95
96  return 1;
97}
98
99#endif
100
101NSString *NGBundleWasLoadedNotificationName = @"NGBundleWasLoadedNotification";
102
103@interface NSBundle(NGBundleManagerPrivate)
104- (BOOL)_loadForBundleManager:(NGBundleManager *)_manager;
105@end
106
107@interface NGBundleManager(PrivateMethods)
108
109- (void)registerBundle:(NSBundle *)_bundle
110  classes:(NSArray *)_classes
111  categories:(NSArray *)_categories;
112
113- (NSString *)pathForBundleProvidingResource:(NSString *)_resourceName
114  ofType:(NSString *)_type
115  resourceSelector:(NGBundleResourceSelector)_selector
116  context:(void *)_ctx;
117
118- (NSString *)makeBundleInfoPath:(NSString *)_path;
119
120@end
121
122static BOOL _selectClassByVersion(NSString        *_resourceName,
123                                  NSString        *_resourceType,
124                                  NSString        *_path,
125                                  NSDictionary    *_resourceConfig,
126                                  NGBundleManager *_bundleManager,
127                                  void            *_version)
128{
129  id  tmp;
130  int classVersion;
131
132  if (![_resourceType isEqualToString:@"classes"])
133    return NO;
134
135  if (_version == NULL)
136    return YES;
137  if ([(id)_version intValue] == -1)
138    return YES;
139
140  if ((tmp = [_resourceConfig objectForKey:@"version"])) {
141    classVersion = [tmp intValue];
142
143    if (classVersion < [(id)_version intValue]) {
144      NSLog(@"WARNING: class version mismatch for class %@: "
145            @"requested at least version %i, got version %i",
146            _resourceName, [(id)_version intValue], classVersion);
147    }
148  }
149  if ((tmp = [_resourceConfig objectForKey:@"exact-version"])) {
150    classVersion = [tmp intValue];
151
152    if (classVersion != [(id)_version intValue]) {
153      NSLog(@"WARNING: class version mismatch for class %@: "
154            @"requested exact version %i, got version %i",
155            _resourceName, [(id)_version intValue], classVersion);
156    }
157  }
158  return YES;
159}
160
161@implementation NGBundleManager
162
163// THREAD
164static NGBundleManager *defaultManager = nil;
165static BOOL debugOn = NO;
166
167#if defined(__MINGW32__)
168static NSString *NGEnvVarPathSeparator = @";";
169#else
170static NSString *NGEnvVarPathSeparator = @":";
171#endif
172
173+ (void)initialize {
174  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
175
176  debugOn = [ud boolForKey:@"NGBundleManagerDebugEnabled"];
177}
178
179+ (id)defaultBundleManager {
180  if (defaultManager == nil) {
181    defaultManager = [[NGBundleManager alloc] init];
182  }
183
184  return defaultManager;
185}
186
187/* setup bundle search path */
188
189- (void)_addMainBundlePathToPathArray:(NSMutableArray *)_paths {
190  NSProcessInfo *pi;
191  NSString *path;
192
193  pi   = [NSProcessInfo processInfo];
194  path = [[pi arguments] objectAtIndex:0];
195  path = [path stringByDeletingLastPathComponent];
196
197  if ([path length] > 0) {
198    // TODO: to be correct this would need to read the bundle-info
199    //       NSExecutable?!
200    /*
201       The path is the complete path to the executable, including the
202       processor, the OS and the library combo. Strip these directories
203       from the main bundle's path.
204    */
205    path = [[[path stringByDeletingLastPathComponent]
206                   stringByDeletingLastPathComponent]
207                   stringByDeletingLastPathComponent];
208    [_paths addObject:path];
209  }
210}
211
212- (void)_addBundlePathDefaultToPathArray:(NSMutableArray *)_paths {
213  NSUserDefaults *ud;
214  id paths;
215
216  if ((ud = [NSUserDefaults standardUserDefaults]) == nil) {
217	// got this with gstep-base during the port, apparently it happens
218	// if the bundle manager is created inside the setup process of
219	// gstep-base (for whatever reason)
220	NSLog(@"ERROR(NGBundleManager): got no system userdefaults object!");
221#if DEBUG
222	abort();
223#endif
224  }
225
226  if ((paths = [ud arrayForKey:@"NGBundlePath"]) == nil) {
227    if ((paths = [ud stringForKey:@"NGBundlePath"]) != nil)
228      paths = [paths componentsSeparatedByString:NGEnvVarPathSeparator];
229  }
230  if (paths != nil)
231    [_paths addObjectsFromArray:paths];
232  else if (debugOn)
233    NSLog(@"Note: NGBundlePath default is not configured.");
234}
235
236- (void)_addEnvironmentPathToPathArray:(NSMutableArray *)_paths {
237  NSProcessInfo *pi;
238  id paths;
239
240  pi = [NSProcessInfo processInfo];
241  paths = [[pi environment] objectForKey:@"NGBundlePath"];
242  if (paths)
243    paths = [paths componentsSeparatedByString:NGEnvVarPathSeparator];
244  if (paths) [_paths addObjectsFromArray:paths];
245}
246
247- (void)_addGNUstepPathsToPathArray:(NSMutableArray *)_paths {
248  /* Old code for old gstep-make and gstep-base.  */
249  NSDictionary *env;
250  NSString     *p;
251  unsigned     i, count;
252  id tmp;
253
254  env = [[NSProcessInfo processInfo] environment];
255
256  if ((tmp = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil)
257    tmp = [env objectForKey:@"GNUSTEP_PATHLIST"];
258  tmp = [tmp componentsSeparatedByString:@":"];
259
260  for (i = 0, count = [tmp count]; i < count; i++) {
261    p = [tmp objectAtIndex:i];
262    p = [p stringByAppendingPathComponent:@"Library"];
263    p = [p stringByAppendingPathComponent:@"Bundles"];
264    if ([self->bundleSearchPaths containsObject:p]) continue;
265
266    if (p) [self->bundleSearchPaths addObject:p];
267  }
268
269  /* New code for new gstep-make and gstep-base.  */
270  tmp = NSStandardLibraryPaths();
271  {
272    NSEnumerator *e = [tmp objectEnumerator];
273    while ((tmp = [e nextObject]) != nil) {
274      tmp = [tmp stringByAppendingPathComponent:@"Bundles"];
275      if ([self->bundleSearchPaths containsObject:tmp])
276	continue;
277
278      [self->bundleSearchPaths addObject:tmp];
279    }
280  }
281}
282
283- (void)_setupBundleSearchPathes {
284  /* setup bundle search path */
285
286  self->bundleSearchPaths = [[NSMutableArray alloc] initWithCapacity:16];
287
288  [self _addMainBundlePathToPathArray:self->bundleSearchPaths];
289  [self _addBundlePathDefaultToPathArray:self->bundleSearchPaths];
290  [self _addEnvironmentPathToPathArray:self->bundleSearchPaths];
291  [self _addGNUstepPathsToPathArray:self->bundleSearchPaths];
292
293#if DEBUG && NeXT_Foundation_LIBRARY && 0
294  NSLog(@"%s: bundle search pathes:\n%@", __PRETTY_FUNCTION__,
295	self->bundleSearchPaths);
296#endif
297}
298
299- (void)_registerLoadedBundles {
300  NSEnumerator *currentBundles;
301  NSBundle     *loadedBundle;
302
303  currentBundles = [[NSBundle allBundles] objectEnumerator];
304  while ((loadedBundle = [currentBundles nextObject]) != nil)
305    [self registerBundle:loadedBundle classes:nil categories:nil];
306}
307
308- (void)_registerForBundleLoadNotification {
309  [[NSNotificationCenter defaultCenter]
310                         addObserver:self
311                         selector:@selector(_bundleDidLoadNotifcation:)
312                         name:@"NSBundleDidLoadNotification"
313                         object:nil];
314}
315
316- (id)init {
317#if GNUSTEP_BASE_LIBRARY
318  if ([NSUserDefaults standardUserDefaults] == nil) {
319    /* called inside setup process, deny creation (HACK) */
320    [self release];
321    return nil;
322  }
323#endif
324
325  if ((self = [super init])) {
326    self->classToBundle =
327      NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
328                       NSNonRetainedObjectMapValueCallBacks,
329                       32);
330    self->classNameToBundle =
331      NSCreateMapTable(NSObjectMapKeyCallBacks,
332                       NSNonRetainedObjectMapValueCallBacks,
333                       32);
334    self->categoryNameToBundle =
335      NSCreateMapTable(NSObjectMapKeyCallBacks,
336                       NSNonRetainedObjectMapValueCallBacks,
337                       32);
338    self->pathToBundle =
339      NSCreateMapTable(NSObjectMapKeyCallBacks,
340                       NSNonRetainedObjectMapValueCallBacks,
341                       32);
342    self->pathToBundleInfo =
343      NSCreateMapTable(NSObjectMapKeyCallBacks,
344                       NSObjectMapValueCallBacks,
345                       32);
346    self->nameToBundle =
347      NSCreateMapTable(NSObjectMapKeyCallBacks,
348                       NSNonRetainedObjectMapValueCallBacks,
349                       32);
350    self->loadedBundles =
351      NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks,
352                       NSObjectMapValueCallBacks,
353                       32);
354
355    [self _setupBundleSearchPathes];
356    [self _registerLoadedBundles];
357    [self _registerForBundleLoadNotification];
358  }
359  return self;
360}
361
362- (void)dealloc {
363  [self->loadingBundles release];
364  if (self->loadedBundles)        NSFreeMapTable(self->loadedBundles);
365  if (self->classToBundle)        NSFreeMapTable(self->classToBundle);
366  if (self->classNameToBundle)    NSFreeMapTable(self->classNameToBundle);
367  if (self->categoryNameToBundle) NSFreeMapTable(self->categoryNameToBundle);
368  if (self->pathToBundle)         NSFreeMapTable(self->pathToBundle);
369  if (self->pathToBundleInfo)     NSFreeMapTable(self->pathToBundleInfo);
370  if (self->nameToBundle)         NSFreeMapTable(self->nameToBundle);
371  [self->bundleSearchPaths release];
372  [super dealloc];
373}
374
375/* accessors */
376
377- (void)setBundleSearchPaths:(NSArray *)_paths {
378  ASSIGNCOPY(self->bundleSearchPaths, _paths);
379}
380- (NSArray *)bundleSearchPaths {
381  return self->bundleSearchPaths;
382}
383
384/* registering bundles */
385
386- (void)registerBundle:(NSBundle *)_bundle
387  classes:(NSArray *)_classes
388  categories:(NSArray *)_categories
389{
390  NSEnumerator *e;
391  id v;
392
393#if NeXT_RUNTIME || APPLE_RUNTIME
394  v = [_bundle bundlePath];
395  if ([v hasSuffix:@"Libraries"] || [v hasSuffix:@"Tools"]) {
396    if (debugOn)
397      fprintf(stderr, "INVALID BUNDLE: %s\n", [[_bundle bundlePath] cString]);
398    return;
399  }
400#endif
401
402#if 0
403  NSLog(@"NGBundleManager: register loaded bundle %@", [_bundle bundlePath]);
404#endif
405
406  e = [_classes objectEnumerator];
407  while ((v = [e nextObject]) != nil) {
408#if NeXT_RUNTIME || APPLE_RUNTIME
409    hookDoLookup = NO;
410#endif
411
412    NSMapInsert(self->classToBundle, NSClassFromString(v), _bundle);
413    NSMapInsert(self->classNameToBundle, v, _bundle);
414
415#if NeXT_RUNTIME || APPLE_RUNTIME
416    hookDoLookup = YES;
417#endif
418  }
419
420  e = [_categories objectEnumerator];
421  while ((v = [e nextObject]) != nil)
422    NSMapInsert(self->categoryNameToBundle, v, _bundle);
423}
424
425/* bundle locator */
426
427- (NSString *)pathForBundleWithName:(NSString *)_name type:(NSString *)_type {
428  NSFileManager *fm = [NSFileManager defaultManager];
429  NSEnumerator  *e;
430  NSString      *path;
431  NSString      *bundlePath;
432  NSBundle      *bundle;
433
434  /* first check in table */
435
436
437  bundlePath = [_name stringByAppendingPathExtension:_type];
438
439  if ((bundle = NSMapGet(self->nameToBundle, bundlePath)))
440    return [bundle bundlePath];
441
442  e = [self->bundleSearchPaths objectEnumerator];
443  while ((path = [e nextObject])) {
444    BOOL isDir = NO;
445
446    if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
447      if (!isDir) continue;
448
449      if ([[path lastPathComponent] isEqualToString:bundlePath]) {
450        // direct match (a bundle was specified in the path)
451        return path;
452      }
453      else {
454        NSString *tmp;
455
456        tmp = [path stringByAppendingPathComponent:bundlePath];
457        if ([fm fileExistsAtPath:tmp isDirectory:&isDir]) {
458          if (isDir)
459            // found bundle
460            return tmp;
461        }
462      }
463    }
464  }
465  return nil;
466}
467
468/* getting bundles */
469
470- (NSBundle *)bundleForClass:(Class)aClass {
471  /* this method never loads a dynamic bundle (since the class is set up) */
472  NSBundle *bundle;
473
474  if (aClass == Nil)
475    return nil;
476
477  bundle = NSMapGet(self->classToBundle, aClass);
478
479#if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
480  if (bundle == nil) {
481    NSString *p;
482
483    bundle = [NSBundle bundleForClass:aClass];
484    if (bundle == [NSBundle mainBundle])
485      bundle = nil;
486    else {
487      p = [bundle bundlePath];
488      if ([p hasSuffix:@"Libraries"]) {
489	if (debugOn) {
490	  fprintf(stderr, "%s: Dylib bundle: 0x%p: %s\n",
491		  __PRETTY_FUNCTION__,
492		  bundle, [[bundle bundlePath] cString]);
493	}
494	bundle = nil;
495      }
496      else if ([p hasSuffix:@"Tools"]) {
497	if (debugOn) {
498	  fprintf(stderr, "%s: Tool bundle: 0x%p: %s\n",
499		  __PRETTY_FUNCTION__,
500		  bundle, [[bundle bundlePath] cString]);
501	}
502	bundle = nil;
503      }
504    }
505  }
506#endif
507  if (bundle == nil) {
508    /*
509      if the class wasn't loaded from a bundle, it's *either* the main bundle
510      or a bundle loaded before NGExtension was loaded !!!
511    */
512
513#if !LIB_FOUNDATION_LIBRARY && !GNUSTEP_BASE_LIBRARY
514    // Note: incorrect behaviour if NGExtensions is dynamically loaded !
515    // TODO: can we do anything about this? Can we detect the situation and
516    //       print a log instead of the compile warning?
517    // Note: the above refers to the situation when a framework is implicitly
518    //       loaded by loading a bundle (the framework is not linked against
519    //       the main tool)
520#endif
521    bundle = [NSBundle mainBundle];
522    NSMapInsert(self->classToBundle,     aClass, bundle);
523    NSMapInsert(self->classNameToBundle, NSStringFromClass(aClass), bundle);
524  }
525  return bundle;
526}
527- (NSBundle *)bundleWithPath:(NSString *)path {
528  NSBundle *bundle = nil;
529  NSString *bn;
530
531  path = [path stringByResolvingSymlinksInPath];
532  if (path == nil)
533    return nil;
534
535  if (debugOn) NSLog(@"find bundle for path: '%@'", path);
536  bundle = NSMapGet(self->pathToBundle, path);
537
538  if (bundle) {
539    if (debugOn) NSLog(@"  found: %@", bundle);
540    return bundle;
541  }
542
543  if ((bundle = [(NGBundle *)[NGBundle alloc] initWithPath:path]) == nil) {
544    [self errorWithFormat: @"could not create bundle for path: '%@'", path];
545    return nil;
546  }
547
548  bn = [[bundle bundleName]
549                stringByAppendingPathExtension:[bundle bundleType]],
550
551  NSMapInsert(self->pathToBundle, path, bundle);
552  NSMapInsert(self->nameToBundle, bn,   bundle);
553  return bundle;
554}
555
556- (NSBundle *)bundleWithName:(NSString *)_name type:(NSString *)_type {
557  NSBundle *bundle;
558  NSString *bn;
559
560  bn     = [_name stringByAppendingPathExtension:_type];
561  bundle = NSMapGet(self->nameToBundle, bn);
562
563  if (![bundle isNotNull]) {
564    bundle = [self bundleWithPath:
565		     [self pathForBundleWithName:_name type:_type]];
566  }
567
568  if (![bundle isNotNull]) /* NSNull is used to signal missing bundles */
569    return nil;
570
571  if (![[bundle bundleType] isEqualToString:_type])
572    return nil;
573
574  /* bundle matches */
575  return bundle;
576}
577- (NSBundle *)bundleWithName:(NSString *)_name {
578  return [self bundleWithName:_name type:@"bundle"];
579}
580
581- (NSBundle *)bundleForClassNamed:(NSString *)_className {
582  NSString *path   = nil;
583  NSBundle *bundle = nil;
584
585  if (_className == nil)
586    return nil;
587
588  /* first check in table */
589
590  if ((bundle = NSMapGet(self->classNameToBundle, _className)) != nil)
591    return bundle;
592
593  path = [self pathForBundleProvidingResource:_className
594               ofType:@"classes"
595               resourceSelector:_selectClassByVersion
596               context:NULL /* version */];
597  if (path != nil) {
598    path = [path stringByResolvingSymlinksInPath];
599    NSAssert(path, @"couldn't resolve symlinks in path ..");
600  }
601
602  if (path == nil)
603    return nil;
604
605  if ((bundle = [self bundleWithPath:path]) != nil)
606    NSMapInsert(self->classNameToBundle, _className, bundle);
607
608  return bundle;
609}
610
611// dependencies
612
613+ (NSInteger)version {
614  return 2;
615}
616
617- (NSArray *)bundlesRequiredByBundle:(NSBundle *)_bundle {
618  [self doesNotRecognizeSelector:_cmd];
619  return nil;
620}
621
622- (NSArray *)classesProvidedByBundle:(NSBundle *)_bundle {
623  return [[_bundle providedResourcesOfType:@"classes"] valueForKey:@"name"];
624}
625- (NSArray *)classesRequiredByBundle:(NSBundle *)_bundle {
626  [self doesNotRecognizeSelector:_cmd];
627  return nil;
628}
629
630/* initialization */
631
632- (NSString *)makeBundleInfoPath:(NSString *)_path {
633#if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) && !defined(GSWARN)
634  return [[[_path stringByAppendingPathComponent:@"Contents"]
635                  stringByAppendingPathComponent:@"Resources"]
636                  stringByAppendingPathComponent:@"bundle-info.plist"];
637#else
638  return [_path stringByAppendingPathComponent:@"bundle-info.plist"];
639#endif
640}
641
642- (id)_initializeLoadedBundle:(NSBundle *)_bundle
643  info:(NSDictionary *)_bundleInfo
644{
645  id handler;
646
647  /* check whether a handler was specified */
648
649  if ((handler = [_bundleInfo objectForKey:@"bundleHandler"]) != nil) {
650    [self debugWithFormat:@"lookup bundle handler %@ of bundle: %@",
651	    handler, _bundle];
652
653    if ((handler = NSClassFromString(handler)) == nil) {
654      NSLog(@"ERROR: did not find handler class %@ of bundle %@.",
655            [_bundleInfo objectForKey:@"bundleHandler"], [_bundle bundlePath]);
656      handler = [_bundle principalClass];
657    }
658
659    handler = [handler alloc];
660
661    if ([handler respondsToSelector:@selector(initForBundle:bundleManager:)])
662      handler = [handler initForBundle:_bundle bundleManager:self];
663    else
664      handler = [handler init];
665    handler = [handler autorelease];
666
667    if (handler == nil) {
668      NSLog(@"ERROR: could not instantiate handler class %@ of bundle %@.",
669            [_bundleInfo objectForKey:@"bundleHandler"], [_bundle bundlePath]);
670      handler = [_bundle principalClass];
671    }
672  }
673  else {
674    [self debugWithFormat:
675	    @"no bundle handler, lookup principal class of bundle: %@",
676	    _bundle];
677    if ((handler = [_bundle principalClass]) == nil) {
678      /* use NGBundle class as default bundle handler */
679#if !(NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY)
680      [self warnWithFormat:@"bundle has no principal class: %@", _bundle];
681#endif
682      handler = [NGBundle class];
683    }
684    else
685      [self debugWithFormat:@"  => %@", handler];
686  }
687
688  return handler;
689}
690
691/* loading */
692
693- (NSDictionary *)_loadBundleInfoAtExistingPath:(NSString *)_path {
694  NSDictionary *bundleInfo;
695  id info;
696
697#if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
698  bundleInfo = NGParsePropertyListFromFile(_path);
699#else
700  bundleInfo = [NSDictionary dictionaryWithContentsOfFile:_path];
701#endif
702  if (bundleInfo == nil) {
703    NSLog(@"could not load bundle-info at path '%@' !", _path);
704    return nil;
705  }
706
707  /* check required bundle manager version */
708  info = [bundleInfo objectForKey:@"requires"];
709  if ((info = [(NSDictionary *)info objectForKey:@"bundleManagerVersion"])) {
710    if ([info intValue] > [[self class] version]) {
711      /* bundle manager version does not match ... */
712      return nil;
713    }
714  }
715  NSMapInsert(self->pathToBundleInfo, _path, bundleInfo);
716  return bundleInfo;
717}
718
719- (NSBundle *)_locateBundleForClassInfo:(NSDictionary *)_classInfo {
720  NSString *className;
721  NSBundle *bundle;
722
723  if (_classInfo == nil)
724    return nil;
725  if ((className = [_classInfo objectForKey:@"name"]) == nil) {
726    NSLog(@"ERROR: missing classname in bundle-info.plist class section !");
727    return nil;
728  }
729
730  // TODO: do we need to check the runtime for already loaded classes?
731  //       Yes, I think so. But avoid recursions
732#if 0
733#if APPLE_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
734  // TODO: HACK, see above. w/o this, we get issues.
735  if ([className hasPrefix:@"NS"])
736    return nil;
737#endif
738#endif
739
740  if ((bundle = [self bundleForClassNamed:className]) == nil) {
741#if 0 // class might be already loaded
742    NSLog(@"ERROR: did not find class %@ required by bundle %@.",
743          className, [_bundle bundlePath]);
744#endif
745  }
746
747  if (debugOn)
748    NSLog(@"CLASS %@ => BUNDLE %@", className, bundle);
749
750  return bundle;
751}
752- (NSArray *)_locateBundlesForClassInfos:(NSEnumerator *)_classInfos {
753  NSMutableArray *requiredBundles;
754  NSDictionary   *i;
755
756  requiredBundles = [NSMutableArray arrayWithCapacity:16];
757  while ((i = [_classInfos nextObject]) != nil) {
758    NSBundle *bundle;
759
760    if ((bundle = [self _locateBundleForClassInfo:i]) == nil)
761      continue;
762
763    [requiredBundles addObject:bundle];
764  }
765  return requiredBundles;
766}
767
768- (BOOL)_preLoadBundle:(NSBundle *)_bundle info:(NSDictionary *)_bundleInfo {
769  /* TODO: split up this huge method */
770  NSDictionary   *requires;
771  NSMutableArray *requiredBundles = nil;
772  NSBundle       *requiredBundle  = nil;
773
774  if (debugOn) NSLog(@"NGBundleManager: preload bundle: %@", _bundle);
775
776  requires = [_bundleInfo objectForKey:@"requires"];
777
778  if (requires == nil)
779    /* invalid bundle info specified */
780    return YES;
781
782  /* load required bundles */
783  {
784    NSEnumerator *e;
785    NSDictionary *i;
786
787    /* locate required bundles */
788
789    e = [[requires objectForKey:@"bundles"] objectEnumerator];
790    while ((i = [e nextObject]) != nil) {
791      NSString *bundleName;
792
793      if (![i respondsToSelector:@selector(objectForKey:)]) {
794        NSLog(@"ERROR(%s): invalid bundle-info of bundle %@ !!!\n"
795              @"  requires-entry is not a dictionary: %@",
796              __PRETTY_FUNCTION__, _bundle, i);
797        continue;
798      }
799
800      if ((bundleName = [i objectForKey:@"name"])) {
801        NSString *type;
802
803        type = [i objectForKey:@"type"];
804        if (type == nil) type = @"bundle";
805
806        if ((requiredBundle = [self bundleWithName:bundleName type:type])) {
807          if (requiredBundles == nil)
808            requiredBundles = [NSMutableArray arrayWithCapacity:16];
809
810          [requiredBundles addObject:requiredBundle];
811        }
812        else {
813          NSLog(@"ERROR(NGBundleManager): did not find bundle '%@' (type=%@) "
814		@"required by bundle %@.",
815                bundleName, type, [_bundle bundlePath]);
816	  continue;
817        }
818      }
819      else
820        NSLog(@"ERROR: error in bundle-info.plist of bundle %@", _bundle);
821    }
822  }
823
824  /* load located bundles */
825  {
826    NSEnumerator *e;
827
828    if (debugOn) {
829      NSLog(@"NGBundleManager:   preload required bundles: %@",
830	    requiredBundles);
831    }
832
833    e = [requiredBundles objectEnumerator];
834    while ((requiredBundle = [e nextObject]) != nil) {
835      Class bundleMaster;
836
837      if ((bundleMaster = [self loadBundle:requiredBundle]) == Nil) {
838        NSLog(@"ERROR: could not load bundle %@ (%@) required by bundle %@.",
839              [requiredBundle bundlePath], requiredBundle,
840	      [_bundle bundlePath]);
841	continue;
842      }
843    }
844  }
845
846  /* load required classes */
847  {
848    NSArray *bundles;
849    NSArray *reqClasses;
850
851    reqClasses = [requires objectForKey:@"classes"];
852
853    bundles = [self _locateBundlesForClassInfos:[reqClasses objectEnumerator]];
854    if (requiredBundles == nil)
855      requiredBundles = [NSMutableArray arrayWithCapacity:16];
856    [requiredBundles addObjectsFromArray:bundles];
857  }
858
859  /* load located bundles */
860  {
861    NSEnumerator *e;
862
863    e = [requiredBundles objectEnumerator];
864    while ((requiredBundle = [e nextObject]) != nil) {
865      Class bundleMaster;
866
867      if ((bundleMaster = [self loadBundle:requiredBundle]) == Nil) {
868        NSLog(@"ERROR: could not load bundle %@ (%@) required by bundle %@.",
869              [requiredBundle bundlePath], requiredBundle,
870	      [_bundle bundlePath]);
871	continue;
872      }
873    }
874  }
875
876  /* check whether versions of classes match */
877  {
878    NSEnumerator *e;
879    NSDictionary *i;
880
881    e = [[requires objectForKey:@"classes"] objectEnumerator];
882    while ((i = [e nextObject]) != nil) {
883      NSString *className;
884      Class clazz;
885
886      if ((className = [i objectForKey:@"name"]) == nil)
887        continue;
888
889      if ((clazz = NSClassFromString(className)) == Nil)
890        continue;
891
892      if ([i objectForKey:@"exact-version"]) {
893        NSInteger v;
894
895        v = [[i objectForKey:@"exact-version"] integerValue];
896
897        if (v != [clazz version]) {
898          NSLog(@"ERROR: required exact class match failed:\n"
899                @"  class:            %@\n"
900                @"  required version: %"PRIiPTR"\n"
901                @"  loaded version:   %"PRIiPTR"\n"
902                @"  bundle:           %@",
903                className,
904                v, [clazz version],
905                [_bundle bundlePath]);
906        }
907      }
908      else if ([i objectForKey:@"version"]) {
909        NSInteger v;
910
911        v = [[i objectForKey:@"version"] intValue];
912
913        if (v > [clazz version]) {
914          NSLog(@"ERROR: provided class does not match required version:\n"
915                @"  class:                  %@\n"
916                @"  least required version: %"PRIiPTR"\n"
917                @"  loaded version:         %"PRIiPTR"\n"
918                @"  bundle:                 %@",
919                className,
920                v, [clazz version],
921                [_bundle bundlePath]);
922        }
923      }
924    }
925  }
926
927  return YES;
928}
929- (BOOL)_postLoadBundle:(NSBundle *)_bundle info:(NSDictionary *)_bundleInfo {
930  return YES;
931}
932
933- (id)loadBundle:(NSBundle *)_bundle {
934  NSString     *path       = nil;
935  NSDictionary *bundleInfo = nil;
936  id bundleManager = nil;
937
938#if DEBUG
939  NSAssert(self->loadedBundles, @"missing loadedBundles hashmap ..");
940#endif
941
942  if ((bundleManager = NSMapGet(self->loadedBundles, _bundle)))
943    return bundleManager;
944
945  if (_bundle == [NSBundle mainBundle])
946    return [NSBundle mainBundle];
947
948  if ([self->loadingBundles containsObject:_bundle])
949    // recursive call
950    return nil;
951
952  if (self->loadingBundles == nil)
953    self->loadingBundles = [[NSMutableSet allocWithZone:[self zone]] init];
954  [self->loadingBundles addObject:_bundle];
955
956  path = [_bundle bundlePath];
957  path = [self makeBundleInfoPath:path];
958
959  if ((bundleInfo = NSMapGet(self->pathToBundleInfo, path)) == nil) {
960    if ([[NSFileManager defaultManager] fileExistsAtPath:path])
961      bundleInfo = [self _loadBundleInfoAtExistingPath:path];
962  }
963
964  if (![self _preLoadBundle:_bundle info:bundleInfo])
965    goto done;
966
967  if (debugOn) NSLog(@"NGBundleManager: will load bundle: %@", _bundle);
968  if (![_bundle _loadForBundleManager:self])
969    goto done;
970  if (debugOn) NSLog(@"NGBundleManager: did load bundle: %@", _bundle);
971
972  if (![self _postLoadBundle:_bundle info:bundleInfo])
973    goto done;
974
975  if ((bundleManager =
976       [self _initializeLoadedBundle:_bundle info:bundleInfo])) {
977    NSMapInsert(self->loadedBundles, _bundle, bundleManager);
978
979    if ([bundleManager respondsToSelector:
980                         @selector(bundleManager:didLoadBundle:)])
981      [bundleManager bundleManager:self didLoadBundle:_bundle];
982  }
983#if 0
984  else {
985    NSLog(@"ERROR(%s): couldn't initialize loaded bundle '%@'",
986          __PRETTY_FUNCTION__, [_bundle bundlePath]);
987  }
988#endif
989 done:
990  [self->loadingBundles removeObject:_bundle];
991
992  if (bundleManager) {
993    if (bundleInfo == nil)
994      bundleInfo = [NSDictionary dictionary];
995
996    [[NSNotificationCenter defaultCenter]
997                           postNotificationName:
998                             NGBundleWasLoadedNotificationName
999                           object:_bundle
1000                           userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
1001                             self,          @"NGBundleManager",
1002                             bundleManager, @"NGBundleHandler",
1003                             bundleInfo,    @"NGBundleInfo",
1004                             nil]];
1005  }
1006  return bundleManager;
1007}
1008
1009// manager
1010
1011- (id)principalObjectOfBundle:(NSBundle *)_bundle {
1012  return (id)NSMapGet(self->loadedBundles, _bundle);
1013}
1014
1015// resources
1016
1017static BOOL _doesInfoMatch(NSArray *keys, NSDictionary *dict, NSDictionary *info)
1018{
1019  int i, count;
1020
1021  for (i = 0, count = [keys count]; i < count; i++) {
1022    NSString *key;
1023    id kv, vv;
1024
1025    key = [keys objectAtIndex:i];
1026    vv  = [info objectForKey:key];
1027
1028    if (vv == nil) {
1029      /* info has no matching key */
1030      return NO;
1031    }
1032
1033    kv = [dict objectForKey:key];
1034    if (![kv isEqual:vv])
1035      return NO;
1036  }
1037  return YES;
1038}
1039
1040- (NSDictionary *)configForResource:(id)_resource ofType:(NSString *)_type
1041  providedByBundle:(NSBundle *)_bundle
1042{
1043  NSDictionary *bundleInfo = nil;
1044  NSString     *infoPath;
1045  NSEnumerator *providedResources;
1046  NSArray      *rnKeys = nil;
1047  id           info;
1048
1049  if ([_resource respondsToSelector:@selector(objectForKey:)]) {
1050    rnKeys     = [_resource allKeys];
1051  }
1052
1053  infoPath = [self makeBundleInfoPath:[_bundle bundlePath]];
1054
1055  /* check whether info is in cache */
1056  if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1057    if (![[NSFileManager defaultManager] fileExistsAtPath:infoPath])
1058      /* no bundle-info.plist available .. */
1059      return nil;
1060
1061    /* load info */
1062    bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1063  }
1064
1065  /* get provided resources config */
1066
1067  providedResources =
1068    [[(NSDictionary *)[bundleInfo objectForKey:@"provides"] objectForKey:_type]
1069                  objectEnumerator];
1070  if (providedResources == nil) return nil;
1071
1072  /* scan provided resources */
1073
1074  while ((info = [providedResources nextObject])) {
1075    if (rnKeys) {
1076      if (!_doesInfoMatch(rnKeys, _resource, info))
1077        continue;
1078    }
1079    else {
1080      NSString *name;
1081
1082      name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1083      if (name == nil) continue;
1084      if (![name isEqualToString:_resource]) continue;
1085    }
1086    return info;
1087  }
1088  return nil;
1089}
1090
1091- (void)_processInfoForProvidedResources:(NSDictionary *)info
1092  ofType:(NSString *)_type path:(NSString *)path
1093  resourceName:(NSString *)_resourceName
1094  resourceSelector:(NGBundleResourceSelector)_selector
1095  context:(void *)_context
1096  andAddToResultArray:(NSMutableArray *)result
1097{
1098  NSEnumerator *providedResources = nil;
1099  if (info == nil) return;
1100
1101  /* direct match (a bundle was specified in the path) */
1102
1103  providedResources = [[(NSDictionary *)[info objectForKey:@"provides"]
1104                              objectForKey:_type]
1105                              objectEnumerator];
1106  info = nil;
1107  if (providedResources == nil) return;
1108
1109  /* scan provide array */
1110  while ((info = [providedResources nextObject])) {
1111    NSString *name;
1112
1113    if ((name = [[info objectForKey:@"name"] stringValue]) == nil)
1114      continue;
1115
1116    if (_resourceName) {
1117      if (![name isEqualToString:_resourceName])
1118	continue;
1119    }
1120    if (_selector) {
1121      if (!_selector(_resourceName, _type, path, info, self, _context))
1122	continue;
1123    }
1124
1125    [result addObject:path];
1126  }
1127}
1128
1129- (NSArray *)pathsForBundlesProvidingResource:(NSString *)_resourceName
1130  ofType:(NSString *)_type
1131  resourceSelector:(NGBundleResourceSelector)_selector
1132  context:(void *)_context
1133{
1134  /* TODO: split up method */
1135  NSMutableArray *result = nil;
1136  NSFileManager  *fm;
1137  NSEnumerator   *e;
1138  NSString       *path;
1139
1140  if (debugOn) {
1141    NSLog(@"BM LOOKUP pathes (%"PRIuPTR" bundles loaded): %@ / %@",
1142          NSCountMapTable(self->loadedBundles), _resourceName, _type);
1143  }
1144
1145  fm     = [NSFileManager defaultManager];
1146  result = [NSMutableArray arrayWithCapacity:64];
1147
1148  // TODO: look in loaded bundles
1149
1150  /* check physical pathes */
1151
1152  e = [self->bundleSearchPaths objectEnumerator];
1153  while ((path = [e nextObject]) != nil) {
1154    NSEnumerator *dir;
1155    BOOL     isDir = NO;
1156    NSString *tmp, *bundleDirPath;
1157    id       info = nil;
1158
1159    if (![fm fileExistsAtPath:path isDirectory:&isDir])
1160      continue;
1161
1162    if (!isDir) continue;
1163
1164    /* check whether an appropriate bundle is contained in 'path' */
1165
1166    dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1167    while ((bundleDirPath = [dir nextObject]) != nil) {
1168      NSDictionary *bundleInfo      = nil;
1169      NSEnumerator *providedResources = nil;
1170      NSString     *infoPath;
1171      id           info;
1172
1173      bundleDirPath = [path stringByAppendingPathComponent:bundleDirPath];
1174      infoPath = [self makeBundleInfoPath:bundleDirPath];
1175
1176      // TODO: can we use _doesBundleInfo:path:providedResource:... ?
1177      if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath))==nil) {
1178	if (![fm fileExistsAtPath:infoPath])
1179	  continue;
1180
1181	bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1182      }
1183
1184      providedResources =
1185	[[(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1186			              objectForKey:_type]
1187	                              objectEnumerator];
1188      if (providedResources == nil) continue;
1189
1190      /* scan 'provides' array */
1191      while ((info = [providedResources nextObject])) {
1192	NSString *name;
1193
1194	name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1195	if (name == nil) continue;
1196
1197	if (_resourceName != nil) {
1198	  if (![name isEqualToString:_resourceName])
1199	    continue;
1200	}
1201	if (_selector != NULL) {
1202	  if (!_selector(name, _type, bundleDirPath, info, self, _context))
1203	    continue;
1204	}
1205
1206	[result addObject:bundleDirPath];
1207	break;
1208      }
1209    }
1210
1211    /* check for direct match (NGBundlePath element is a bundle) */
1212
1213    tmp = [self makeBundleInfoPath:path];
1214
1215    if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1216      if ([fm fileExistsAtPath:tmp])
1217	info = [self _loadBundleInfoAtExistingPath:tmp];
1218    }
1219
1220    [self _processInfoForProvidedResources:info ofType:_type path:path
1221	  resourceName:_resourceName resourceSelector:_selector
1222	  context:_context
1223	  andAddToResultArray:result];
1224  }
1225
1226  if ([result count] == 0) {
1227    [self logWithFormat:
1228	    @"Note(%s): method does not search in loaded bundles for "
1229	    @"resources of type '%@'",
1230	    __PRETTY_FUNCTION__, _type];
1231  }
1232
1233  return [[result copy] autorelease];
1234}
1235
1236- (BOOL)_doesBundleInfo:(NSDictionary *)_bundleInfo path:(NSString *)_path
1237  provideResource:(id)_resourceName ofType:(NSString *)_type
1238  rnKeys:(NSArray *)_rnKeys
1239  resourceSelector:(NGBundleResourceSelector)_selector context:(void *)_context
1240{
1241  NSEnumerator *providedResources;
1242  NSDictionary *info;
1243
1244  providedResources =
1245    [[(NSDictionary *)[_bundleInfo objectForKey:@"provides"]
1246                      objectForKey:_type] objectEnumerator];
1247  if (providedResources == nil) return NO;
1248
1249  /* scan provide array */
1250  while ((info = [providedResources nextObject])) {
1251    if (_rnKeys != nil) {
1252      if (!_doesInfoMatch(_rnKeys, _resourceName, info))
1253        continue;
1254    }
1255    else {
1256      NSString *name;
1257
1258      name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1259      if (name == nil) continue;
1260      if (![name isEqualToString:_resourceName]) continue;
1261    }
1262
1263    if (_selector != NULL) {
1264      if (!_selector(_resourceName, _type, _path, info, self, _context))
1265        continue;
1266    }
1267
1268    /* all conditions applied (found) */
1269    return YES;
1270  }
1271  return NO;
1272}
1273
1274- (NSString *)pathOfLoadedBundleProvidingResource:(id)_resourceName
1275  ofType:(NSString *)_type
1276  resourceSelector:(NGBundleResourceSelector)_selector context:(void *)_context
1277{
1278  NSMapEnumerator menum;
1279  NSString     *path;
1280  NSDictionary *bundleInfo;
1281  NSArray      *rnKeys;
1282
1283  rnKeys = ([_resourceName respondsToSelector:@selector(objectForKey:)])
1284    ? [_resourceName allKeys]
1285    : (NSArray *)nil;
1286
1287  menum = NSEnumerateMapTable(self->pathToBundleInfo);
1288  while (NSNextMapEnumeratorPair(&menum, (void *)&path, (void *)&bundleInfo)) {
1289    if (debugOn) {
1290      NSLog(@"check loaded bundle for resource %@: %@", _resourceName,
1291            path);
1292    }
1293
1294    if ([self _doesBundleInfo:bundleInfo path:path
1295              provideResource:_resourceName ofType:_type rnKeys:rnKeys
1296              resourceSelector:_selector context:_context])
1297      /* strip bundle-info.plist name */
1298      return [path stringByDeletingLastPathComponent];
1299  }
1300
1301  return nil;
1302}
1303
1304- (NSString *)pathForBundleProvidingResource:(id)_resourceName
1305  ofType:(NSString *)_type
1306  resourceSelector:(NGBundleResourceSelector)_selector
1307  context:(void *)_context
1308{
1309  /* main path lookup method */
1310  // TODO: this method seriously needs some refactoring
1311  NSFileManager *fm;
1312  NSEnumerator  *e;
1313  NSString      *path;
1314  NSArray       *rnKeys = nil;
1315
1316  if (debugOn) {
1317    NSLog(@"BM LOOKUP path (%"PRIuPTR" bundles loaded): %@ / %@",
1318          NSCountMapTable(self->loadedBundles), _resourceName, _type);
1319  }
1320
1321  /* look in loaded bundles */
1322
1323  path = [self pathOfLoadedBundleProvidingResource:_resourceName ofType:_type
1324               resourceSelector:_selector context:_context];
1325  if (path != nil) return path;
1326
1327  /* look in filesystem */
1328
1329  if ([_resourceName respondsToSelector:@selector(objectForKey:)]) {
1330    rnKeys     = [_resourceName allKeys];
1331  }
1332
1333  fm = [NSFileManager defaultManager];
1334  e = [self->bundleSearchPaths objectEnumerator];
1335  while ((path = [e nextObject]) != nil) {
1336    NSEnumerator *dir;
1337    BOOL     isDir = NO;
1338    NSString *tmp;
1339    id       info = nil;
1340
1341    if (![fm fileExistsAtPath:path isDirectory:&isDir])
1342      continue;
1343
1344    if (!isDir) continue;
1345
1346    /* check whether an appropriate bundle is contained in 'path' */
1347
1348    dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1349    while ((tmp = [dir nextObject]) != nil) {
1350      NSDictionary *bundleInfo      = nil;
1351      NSString     *infoPath;
1352
1353      tmp      = [path stringByAppendingPathComponent:tmp];
1354      infoPath = [self makeBundleInfoPath:tmp];
1355
1356      if (debugOn)
1357        NSLog(@"check path path=%@ info=%@", tmp, infoPath);
1358
1359      if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1360        if (![fm fileExistsAtPath:infoPath])
1361          continue;
1362
1363        bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1364      }
1365      if (debugOn)
1366        NSLog(@"found info for path=%@ info=%@: %@", tmp,infoPath,bundleInfo);
1367
1368      if ([self _doesBundleInfo:bundleInfo path:tmp
1369                provideResource:_resourceName ofType:_type rnKeys:rnKeys
1370                resourceSelector:_selector context:_context])
1371        return tmp;
1372    }
1373
1374    /* check for direct match */
1375
1376    tmp = [self makeBundleInfoPath:path];
1377
1378    if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1379        if ([fm fileExistsAtPath:tmp])
1380          info = [self _loadBundleInfoAtExistingPath:tmp];
1381        else if (debugOn) {
1382          NSLog(@"WARNING(%s): did not find direct path '%@'",
1383                __PRETTY_FUNCTION__, tmp);
1384        }
1385    }
1386
1387    if (info != nil) {
1388        // direct match (a bundle was specified in the path)
1389        NSEnumerator *providedResources;
1390        NSDictionary *provides;
1391
1392        provides          = [(NSDictionary *)info objectForKey:@"provides"];
1393        providedResources = [[provides objectForKey:_type] objectEnumerator];
1394        info              = nil;
1395        if (providedResources == nil) continue;
1396
1397        // scan provide array
1398        while ((info = [providedResources nextObject])) {
1399          if (rnKeys) {
1400            if (!_doesInfoMatch(rnKeys, _resourceName, info))
1401              continue;
1402          }
1403          else {
1404            NSString *name;
1405
1406            name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1407            if (name == nil) continue;
1408            if (![name isEqualToString:_resourceName]) continue;
1409          }
1410
1411          if (_selector) {
1412            if (!_selector(_resourceName, _type, tmp, info, self, _context))
1413              continue;
1414          }
1415          /* all conditions applied */
1416          return tmp;
1417        }
1418    }
1419  }
1420  return nil;
1421}
1422
1423- (NSBundle *)bundleProvidingResource:(id)_name ofType:(NSString *)_type {
1424  NSString *bp;
1425
1426  if (debugOn) NSLog(@"BM LOOKUP: %@ / %@", _name, _type);
1427
1428  bp = [self pathForBundleProvidingResource:_name
1429             ofType:_type
1430             resourceSelector:NULL context:nil];
1431  if ([bp length] == 0) {
1432#if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) && HEAVY_DEBUG
1433    NSLog(@"%s: found no resource '%@' of type '%@' ...",
1434          __PRETTY_FUNCTION__, _resourceName, _resourceType);
1435#endif
1436    if (debugOn) NSLog(@"  did not find: %@ / %@", _name, _type);
1437    return nil;
1438  }
1439
1440  if (debugOn) NSLog(@"  FOUND: %@", bp);
1441  return [self bundleWithPath:bp];
1442}
1443
1444- (NSArray *)bundlesProvidingResource:(id)_resourceName
1445  ofType:(NSString *)_type
1446{
1447  NSArray        *paths;
1448  NSMutableArray *bundles;
1449  int i, count;
1450
1451  paths = [self pathsForBundlesProvidingResource:_resourceName
1452                ofType:_type
1453                resourceSelector:NULL context:nil];
1454
1455  count = [paths count];
1456  if (paths == nil) return nil;
1457  if (count == 0)   return paths;
1458
1459  bundles = [NSMutableArray arrayWithCapacity:count];
1460  for (i = 0; i < count; i++) {
1461    NSBundle *bundle;
1462
1463    if ((bundle = [self bundleWithPath:[paths objectAtIndex:i]]))
1464      [bundles addObject:bundle];
1465  }
1466  return [[bundles copy] autorelease];
1467}
1468
1469- (NSArray *)providedResourcesOfType:(NSString *)_resourceType
1470  inBundle:(NSBundle *)_bundle
1471{
1472  NSString     *path;
1473  NSDictionary *bundleInfo;
1474
1475  path = [self makeBundleInfoPath:[_bundle bundlePath]];
1476  if (path == nil) return nil;
1477
1478  /* retrieve bundle info dictionary */
1479  if ((bundleInfo = NSMapGet(self->pathToBundleInfo, path)) == nil)
1480    bundleInfo = [self _loadBundleInfoAtExistingPath:path];
1481
1482  return [(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1483                                      objectForKey:_resourceType];
1484}
1485
1486- (void)_addRegisteredProvidedResourcesOfType:(NSString *)_type
1487  toSet:(NSMutableSet *)_result
1488{
1489  NSMapEnumerator menum;
1490  NSString     *path;
1491  NSDictionary *bundleInfo;
1492
1493  menum = NSEnumerateMapTable(self->pathToBundleInfo);
1494  while (NSNextMapEnumeratorPair(&menum, (void *)&path, (void *)&bundleInfo)) {
1495    NSArray *providedResources;
1496
1497    if (debugOn)
1498      NSLog(@"check loaded bundle for resource types %@: %@", _type, path);
1499
1500    providedResources =
1501      [(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1502                       objectForKey:_type];
1503    if (providedResources == nil) continue;
1504
1505    [_result addObjectsFromArray:providedResources];
1506  }
1507}
1508
1509- (NSArray *)providedResourcesOfType:(NSString *)_resourceType {
1510  NSMutableSet  *result = nil;
1511  NSFileManager *fm = [NSFileManager defaultManager];
1512  NSEnumerator  *e;
1513  NSString      *path;
1514
1515  result = [NSMutableSet setWithCapacity:128];
1516
1517  /* scan loaded bundles */
1518
1519  [self _addRegisteredProvidedResourcesOfType:_resourceType toSet:result];
1520
1521  /* scan all bundle search paths */
1522
1523  e = [self->bundleSearchPaths objectEnumerator];
1524  while ((path = [e nextObject]) != nil) {
1525    NSEnumerator *dir;
1526    BOOL     isDir = NO;
1527    NSString *tmp;
1528    id       info = nil;
1529
1530    if (![fm fileExistsAtPath:path isDirectory:&isDir])
1531      continue;
1532    if (!isDir) continue;
1533
1534    /* check whether an appropriate bundle is contained in 'path' */
1535
1536    // TODO: move to own method
1537    dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1538    while ((tmp = [dir nextObject]) != nil) {
1539      NSDictionary *bundleInfo      = nil;
1540      NSArray      *providedResources = nil;
1541      NSString     *infoPath;
1542
1543      tmp = [path stringByAppendingPathComponent:tmp];
1544      infoPath = [self makeBundleInfoPath:tmp];
1545
1546#if 0
1547      NSLog(@"  info path: %@", tmp);
1548#endif
1549
1550      if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1551        if (![fm fileExistsAtPath:infoPath])
1552          continue;
1553
1554        bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1555      }
1556
1557      providedResources =
1558        [(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1559                         objectForKey:_resourceType];
1560      if (providedResources == nil) continue;
1561
1562      [result addObjectsFromArray:providedResources];
1563    }
1564
1565    /* check for direct match */
1566
1567    tmp = [self makeBundleInfoPath:path];
1568
1569    if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1570      if ([fm fileExistsAtPath:tmp])
1571        info = [self _loadBundleInfoAtExistingPath:tmp];
1572    }
1573
1574    if (info != nil) {
1575      // direct match (a bundle was specified in the path)
1576      NSArray      *providedResources;
1577      NSDictionary *provides;
1578
1579      provides          = [(NSDictionary *)info objectForKey:@"provides"];
1580      providedResources = [provides objectForKey:_resourceType];
1581      info = nil;
1582      if (providedResources == nil) continue;
1583
1584      [result addObjectsFromArray:providedResources];
1585    }
1586  }
1587  return [result allObjects];
1588}
1589
1590- (NSBundle *)bundleProvidingResourceOfType:(NSString *)_resourceType
1591  matchingQualifier:(EOQualifier *)_qual
1592{
1593  NSFileManager  *fm = [NSFileManager defaultManager];
1594  NSEnumerator   *e;
1595  NSString       *path;
1596
1597  /* foreach search path entry */
1598
1599  e = [self->bundleSearchPaths objectEnumerator];
1600  while ((path = [e nextObject])) {
1601    BOOL isDir = NO;
1602
1603    if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1604      NSString *tmp;
1605      id info = nil;
1606      if (!isDir) continue;
1607
1608      /* check whether an appropriate bundle is contained in 'path' */
1609      {
1610        NSEnumerator *dir;
1611
1612        dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1613        while ((tmp = [dir nextObject])) {
1614          NSDictionary *bundleInfo;
1615          NSArray      *providedResources;
1616          NSString     *infoPath;
1617
1618          tmp      = [path stringByAppendingPathComponent:tmp];
1619          infoPath = [self makeBundleInfoPath:tmp];
1620
1621          if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1622            if (![fm fileExistsAtPath:infoPath])
1623              continue;
1624
1625            bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1626          }
1627
1628          bundleInfo        = [bundleInfo objectForKey:@"provides"];
1629          providedResources = [bundleInfo objectForKey:_resourceType];
1630          bundleInfo        = nil;
1631          if (providedResources == nil) continue;
1632
1633          providedResources =
1634            [providedResources filteredArrayUsingQualifier:_qual];
1635
1636          if ([providedResources count] > 0)
1637            return [self bundleWithPath:tmp];
1638        }
1639      }
1640
1641      /* check for direct match */
1642
1643      tmp = [self makeBundleInfoPath:path];
1644
1645      if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1646        if ([fm fileExistsAtPath:tmp])
1647          info = [self _loadBundleInfoAtExistingPath:tmp];
1648      }
1649
1650      if (info) {
1651        // direct match (a bundle was specified in the path)
1652        NSArray      *providedResources;
1653        NSDictionary *provides;
1654
1655        provides          = [(NSDictionary *)info objectForKey:@"provides"];
1656        providedResources = [provides objectForKey:_resourceType];
1657        info = nil;
1658        if (providedResources == nil) continue;
1659
1660        providedResources =
1661          [providedResources filteredArrayUsingQualifier:_qual];
1662
1663        if ([providedResources count] > 0)
1664          return [self bundleWithPath:path];
1665      }
1666    }
1667  }
1668  return nil;
1669}
1670
1671- (NSBundle *)bundlesProvidingResourcesOfType:(NSString *)_resourceType
1672  matchingQualifier:(EOQualifier *)_qual
1673{
1674  NSMutableArray *bundles = nil;
1675  NSFileManager  *fm = [NSFileManager defaultManager];
1676  NSEnumerator   *e;
1677  NSString       *path;
1678
1679  bundles = [NSMutableArray arrayWithCapacity:128];
1680
1681  /* foreach search path entry */
1682
1683  e = [self->bundleSearchPaths objectEnumerator];
1684  while ((path = [e nextObject])) {
1685    BOOL isDir = NO;
1686
1687    if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1688      NSString *tmp;
1689      id info = nil;
1690      if (!isDir) continue;
1691
1692      /* check whether an appropriate bundle is contained in 'path' */
1693      {
1694        NSEnumerator *dir;
1695
1696        dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1697        while ((tmp = [dir nextObject])) {
1698          NSDictionary *bundleInfo      = nil;
1699          NSArray      *providedResources = nil;
1700          NSString     *infoPath;
1701
1702          tmp = [path stringByAppendingPathComponent:tmp];
1703          infoPath = [self makeBundleInfoPath:tmp];
1704
1705          if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1706            if (![fm fileExistsAtPath:infoPath])
1707              continue;
1708
1709            bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1710          }
1711
1712          bundleInfo        = [bundleInfo objectForKey:@"provides"];
1713          providedResources = [bundleInfo objectForKey:_resourceType];
1714          bundleInfo        = nil;
1715          if (providedResources == nil) continue;
1716
1717          providedResources =
1718            [providedResources filteredArrayUsingQualifier:_qual];
1719
1720          if ([providedResources count] > 0)
1721            [bundles addObject:[self bundleWithPath:tmp]];
1722        }
1723      }
1724
1725      /* check for direct match */
1726
1727      tmp = [self makeBundleInfoPath:path];
1728
1729      if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1730        if ([fm fileExistsAtPath:tmp])
1731          info = [self _loadBundleInfoAtExistingPath:tmp];
1732      }
1733
1734      if (info) {
1735        // direct match (a bundle was specified in the path)
1736        NSArray      *providedResources;
1737        NSDictionary *provides;
1738
1739        provides          = [(NSDictionary *)info objectForKey:@"provides"];
1740        providedResources = [provides objectForKey:_resourceType];
1741        info = nil;
1742        if (providedResources == nil) continue;
1743
1744        providedResources =
1745          [providedResources filteredArrayUsingQualifier:_qual];
1746
1747        if ([providedResources count] > 0)
1748          [bundles addObject:[self bundleWithPath:path]];
1749      }
1750    }
1751  }
1752  return [[bundles copy] autorelease];
1753}
1754
1755/* notifications */
1756
1757- (void)_bundleDidLoadNotifcation:(NSNotification *)_notification {
1758  NSDictionary *ui = [_notification userInfo];
1759
1760#if 0
1761  NSLog(@"bundle %@ did load with classes %@",
1762        [[_notification object] bundlePath],
1763        [ui objectForKey:@"NSLoadedClasses"]);
1764#endif
1765
1766  [self registerBundle:[_notification object]
1767        classes:[ui objectForKey:@"NSLoadedClasses"]
1768        categories:[ui objectForKey:@"NSLoadedCategories"]];
1769}
1770
1771/* debugging */
1772
1773- (BOOL)isDebuggingEnabled {
1774  return debugOn;
1775}
1776
1777@end /* NGBundleManager */
1778
1779@implementation NSBundle(BundleManagerSupport)
1780
1781+ (id)alloc {
1782  return [NGBundle alloc];
1783}
1784+ (id)allocWithZone:(NSZone *)zone {
1785  return [NGBundle allocWithZone:zone];
1786}
1787
1788#if !(NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY)
1789//#warning remember, bundleForClass is not overridden !
1790#if 0
1791+ (NSBundle *)bundleForClass:(Class)aClass {
1792  return [[NGBundleManager defaultBundleManager] bundleForClass:aClass];
1793}
1794#endif
1795+ (NSBundle *)bundleWithPath:(NSString*)path {
1796  return [[NGBundleManager defaultBundleManager] bundleWithPath:path];
1797}
1798#endif
1799
1800@end /* NSBundle(BundleManagerSupport) */
1801
1802@implementation NSBundle(NGBundleManagerExtensions)
1803
1804- (id)principalObject {
1805  return [[NGBundleManager defaultBundleManager]
1806                           principalObjectOfBundle:self];
1807}
1808
1809- (NSArray *)providedResourcesOfType:(NSString *)_resourceType {
1810  return [[NGBundleManager defaultBundleManager]
1811                           providedResourcesOfType:_resourceType
1812                           inBundle:self];
1813}
1814
1815- (NSString *)bundleName {
1816  return [[[self bundlePath] lastPathComponent] stringByDeletingPathExtension];
1817}
1818
1819- (NSString *)bundleType {
1820  return [[self bundlePath] pathExtension];
1821}
1822
1823- (NSArray *)providedClasses {
1824  return [[NGBundleManager defaultBundleManager] classesProvidedByBundle:self];
1825}
1826
1827- (NSArray *)requiredClasses {
1828  return [[NGBundleManager defaultBundleManager] classesRequiredByBundle:self];
1829}
1830
1831- (NSArray *)requiredBundles {
1832  return [[NGBundleManager defaultBundleManager] bundlesRequiredByBundle:self];
1833}
1834
1835- (NSDictionary *)configForResource:(id)_resource ofType:(NSString *)_type {
1836  return [[NGBundleManager defaultBundleManager]
1837                           configForResource:_resource ofType:_type
1838                           providedByBundle:self];
1839}
1840
1841/* loading */
1842
1843- (BOOL)_loadForBundleManager:(NGBundleManager *)_manager {
1844  return [self load];
1845}
1846
1847@end /* NSBundle(NGBundleManagerExtensions) */
1848
1849@implementation NSBundle(NGLanguageResourceExtensions)
1850
1851static BOOL debugLanguageLookup = NO;
1852
1853// locating resources
1854
1855- (NSString *)pathForResource:(NSString *)_name ofType:(NSString *)_ext
1856  inDirectory:(NSString *)_directory
1857  languages:(NSArray *)_languages
1858{
1859  NSFileManager *fm;
1860  NSString      *path = nil;
1861  int i, langCount;
1862  id (*objAtIdx)(id,SEL,int);
1863
1864  if (debugLanguageLookup) {
1865    NSLog(@"LOOKUP(%s): %@ | %@ | %@ | %@", __PRETTY_FUNCTION__,
1866	  _name, _ext, _directory, [_languages componentsJoinedByString:@","]);
1867  }
1868
1869  path = [self bundlePath];
1870  if ([_directory isNotNull]) {
1871    // TODO: should we change that?
1872    path = [path stringByAppendingPathComponent:_directory];
1873  }
1874  else {
1875#if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY)
1876    path = [path stringByAppendingPathComponent:@"Contents"];
1877#endif
1878    path = [path stringByAppendingPathComponent:@"Resources"];
1879  }
1880
1881  if (debugLanguageLookup) NSLog(@"  BASE: %@", path);
1882
1883  fm   = [NSFileManager defaultManager];
1884  if (![fm fileExistsAtPath:path])
1885    return nil;
1886
1887  if (_ext != nil) _name = [_name stringByAppendingPathExtension:_ext];
1888
1889  langCount = [_languages count];
1890  objAtIdx = (langCount > 0)
1891    ? (void*)[_languages methodForSelector:@selector(objectAtIndex:)]
1892    : NULL;
1893
1894  for (i = 0; i < langCount; i++) {
1895    NSString *language;
1896    NSString *lpath;
1897
1898    language = objAtIdx
1899      ? objAtIdx(_languages, @selector(objectAtIndex:), i)
1900      : [_languages objectAtIndex:i];
1901
1902    language = [language stringByAppendingPathExtension:@"lproj"];
1903    lpath = [path stringByAppendingPathComponent:language];
1904    lpath = [lpath stringByAppendingPathComponent:_name];
1905
1906    if ([fm fileExistsAtPath:lpath])
1907      return lpath;
1908  }
1909
1910  if (debugLanguageLookup)
1911    NSLog(@"  no language matched, check base: %@", path);
1912
1913  /* now look into x.bundle/Resources/name.type */
1914  if ([fm fileExistsAtPath:[path stringByAppendingPathComponent:_name]])
1915    return [path stringByAppendingPathComponent:_name];
1916
1917  return nil;
1918}
1919
1920- (NSString *)pathForResource:(NSString *)_name ofType:(NSString *)_ext
1921  languages:(NSArray *)_languages
1922{
1923  NSString *path;
1924
1925  path = [self pathForResource:_name ofType:_ext
1926               inDirectory:@"Resources"
1927               languages:_languages];
1928  if (path) return path;
1929
1930  path = [self pathForResource:_name ofType:_ext
1931               inDirectory:nil
1932               languages:_languages];
1933  return path;
1934}
1935
1936@end /* NSBundle(NGLanguageResourceExtensions) */
1937
1938@implementation NGBundle
1939
1940+ (id)alloc {
1941  return [self allocWithZone:NULL];
1942}
1943+ (id)allocWithZone:(NSZone*)zone {
1944  return NSAllocateObject(self, 0, zone);
1945}
1946
1947- (id)initWithPath:(NSString *)__path {
1948  return [super initWithPath:__path];
1949}
1950
1951/* loading */
1952
1953- (BOOL)_loadForBundleManager:(NGBundleManager *)_manager {
1954  return [super load];
1955}
1956
1957- (BOOL)load {
1958  NGBundleManager *bm;
1959
1960  bm = [NGBundleManager defaultBundleManager];
1961
1962  return [bm loadBundle:self] ? YES : NO;
1963}
1964
1965+ (NSBundle *)bundleForClass:(Class)aClass {
1966  return [[NGBundleManager defaultBundleManager] bundleForClass:aClass];
1967}
1968+ (NSBundle *)bundleWithPath:(NSString*)path {
1969  return [[NGBundleManager defaultBundleManager] bundleWithPath:path];
1970}
1971
1972#if GNUSTEP_BASE_LIBRARY
1973
1974- (Class)principalClass {
1975  Class c;
1976  NSString *cname;
1977
1978  if ((c = [super principalClass]) != Nil)
1979    return c;
1980
1981  if ((cname = [[self infoDictionary] objectForKey:@"NSPrincipalClass"]) ==nil)
1982    return Nil;
1983
1984  if ((c = NSClassFromString(cname)) != Nil)
1985    return c;
1986
1987  NSLog(@"%s: did not find principal class named '%@' of bundle %@, dict: %@",
1988	__PRETTY_FUNCTION__, cname, self, [self infoDictionary]);
1989  return Nil;
1990}
1991
1992/* description */
1993
1994- (NSString *)description {
1995  char buffer[1024];
1996
1997  sprintf (buffer,
1998	   "<%s %p fullPath: %s infoDictionary: %p loaded=%s>",
1999	   (char*)class_getName([self class]),
2000	   self,
2001	   [[self bundlePath] cString],
2002	   [self infoDictionary],
2003	   [self isLoaded] ? "yes" : "no");
2004
2005  return [NSString stringWithCString:buffer];
2006}
2007#endif
2008
2009@end /* NGBundle */
2010