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