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