]> err.no Git - sope/blob - sope-core/NGExtensions/NGBundleManager.m
fixed another logging problem
[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   return [[_bundle providedResourcesOfType:@"classes"] valueForKey:@"name"];
682 }
683 - (NSArray *)classesRequiredByBundle:(NSBundle *)_bundle {
684   [self doesNotRecognizeSelector:_cmd];
685   return nil;
686 }
687
688 /* initialization */
689
690 - (NSString *)makeBundleInfoPath:(NSString *)_path {
691 #if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) && !defined(GSWARN)
692   return [[[_path stringByAppendingPathComponent:@"Contents"]
693                   stringByAppendingPathComponent:@"Resources"]
694                   stringByAppendingPathComponent:@"bundle-info.plist"];
695 #else
696   return [_path stringByAppendingPathComponent:@"bundle-info.plist"];
697 #endif
698 }
699
700 - (id)_initializeLoadedBundle:(NSBundle *)_bundle
701   info:(NSDictionary *)_bundleInfo
702 {
703   id handler;
704   
705   /* check whether a handler was specified */
706   
707   if ((handler = [_bundleInfo objectForKey:@"bundleHandler"]) != nil) {
708     [self debugWithFormat:@"lookup bundle handler %@ of bundle: %@",
709             handler, _bundle];
710     
711     if ((handler = NSClassFromString(handler)) == nil) {
712       NSLog(@"ERROR: did not find handler class %@ of bundle %@.",
713             [_bundleInfo objectForKey:@"bundleHandler"], [_bundle bundlePath]);
714       handler = [_bundle principalClass];
715     }
716     
717     handler = [handler alloc];
718     
719     if ([handler respondsToSelector:@selector(initForBundle:bundleManager:)])
720       handler = [handler initForBundle:_bundle bundleManager:self];
721     else
722       handler = [handler init];
723     handler = [handler autorelease];
724     
725     if (handler == nil) {
726       NSLog(@"ERROR: could not instantiate handler class %@ of bundle %@.",
727             [_bundleInfo objectForKey:@"bundleHandler"], [_bundle bundlePath]);
728       handler = [_bundle principalClass];
729     }
730   }
731   else {
732     [self debugWithFormat:
733             @"no bundle handler, lookup principal class of bundle: %@",
734             _bundle];
735     if ((handler = [_bundle principalClass]) == nil)
736       /* use NGBundle class as default bundle handler */
737       handler = [NGBundle class];
738   }
739   
740   return handler;
741 }
742
743 /* loading */
744
745 - (NSDictionary *)_loadBundleInfoAtExistingPath:(NSString *)_path {
746   NSDictionary *bundleInfo;
747   id info;
748
749 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
750   bundleInfo = NGParsePropertyListFromFile(_path);
751 #else
752   bundleInfo = [NSDictionary dictionaryWithContentsOfFile:_path];
753 #endif
754   if (bundleInfo == nil) {
755     NSLog(@"couldn't load bundle-info at path '%@' !", _path);
756     return nil;
757   }
758   
759   /* check required bundle manager version */
760   info = [bundleInfo objectForKey:@"requires"];
761   if ((info = [(NSDictionary *)info objectForKey:@"bundleManagerVersion"])) {
762     if ([info intValue] > [[self class] version]) {
763       /* bundle manager version does not match ... */
764       return nil;
765     }
766   }
767   NSMapInsert(self->pathToBundleInfo, _path, bundleInfo);
768   return bundleInfo;
769 }
770
771 - (NSBundle *)_locateBundleForClassInfo:(NSDictionary *)_classInfo {
772   NSString *className;
773   NSBundle *bundle;
774   
775   if (_classInfo == nil)
776     return nil;
777   if ((className = [_classInfo objectForKey:@"name"]) == nil) {
778     NSLog(@"ERROR: missing classname in bundle-info.plist class section !");
779     return nil;
780   }
781   
782   if ((bundle = [self bundleForClassNamed:className]) == nil) {
783 #if 0 // class might be already loaded
784     NSLog(@"ERROR: did not find class %@ required by bundle %@.",
785           className, [_bundle bundlePath]);
786 #endif
787   }
788   
789   return bundle;
790 }
791 - (NSArray *)_locateBundlesForClassInfos:(NSEnumerator *)_classInfos {
792   NSMutableArray *requiredBundles;
793   NSDictionary   *i;
794   
795   requiredBundles = [NSMutableArray arrayWithCapacity:16];
796   while ((i = [_classInfos nextObject])) {
797     NSBundle *bundle;
798     
799     if ((bundle = [self _locateBundleForClassInfo:i]) == nil)
800       continue;
801     
802     [requiredBundles addObject:bundle];
803   }
804   return requiredBundles;
805 }
806
807 - (BOOL)_preLoadBundle:(NSBundle *)_bundle info:(NSDictionary *)_bundleInfo {
808   /* TODO: split up this huge method */
809   NSDictionary   *requires      = nil;
810   NSMutableArray *requiredBundles = nil;
811   NSBundle       *requiredBundle  = nil;
812
813   requires = [_bundleInfo objectForKey:@"requires"];
814   
815   if (requires == nil)
816     /* invalid bundle info specified */
817     return YES;
818
819   /* load required bundles */
820   {
821     NSEnumerator *e;
822     NSDictionary *i;
823
824     /* locate required bundles */
825     
826     e = [[requires objectForKey:@"bundles"] objectEnumerator];
827     while ((i = [e nextObject])) {
828       NSString *bundleName;
829       
830       if (![i respondsToSelector:@selector(objectForKey:)]) {
831         NSLog(@"ERROR(%s): invalid bundle-info of bundle %@ !!!\n"
832               @"  requires-entry is not a dictionary: %@",
833               __PRETTY_FUNCTION__, _bundle, i);
834         continue;
835       }
836       
837       if ((bundleName = [i objectForKey:@"name"])) {
838         NSString *type;
839         
840         type = [i objectForKey:@"type"];
841         if (type == nil) type = @"bundle";
842
843         if ((requiredBundle = [self bundleWithName:bundleName type:type])) {
844           if (requiredBundles == nil)
845             requiredBundles = [NSMutableArray arrayWithCapacity:16];
846           
847           [requiredBundles addObject:requiredBundle];
848         }
849         else {
850           NSLog(@"ERROR(NGBundleManager): did not find bundle '%@' (type=%@) "
851                 @"required by bundle %@.",
852                 bundleName, type, [_bundle bundlePath]);
853           continue;
854         }
855       }
856       else
857         NSLog(@"ERROR: error in bundle-info.plist of bundle %@", _bundle);
858     }
859   }
860
861   /* load located bundles */
862   {
863     NSEnumerator *e;
864     
865     e = [requiredBundles objectEnumerator];
866     while ((requiredBundle = [e nextObject])) {
867       Class bundleMaster;
868       
869       if ((bundleMaster = [self loadBundle:requiredBundle]) == Nil) {
870         NSLog(@"ERROR: could not load bundle %@ (%@) required by bundle %@.",
871               [requiredBundle bundlePath], requiredBundle, [_bundle bundlePath]);
872         continue;
873       }
874     }
875   }
876
877   /* load required classes */
878   {
879     NSArray *bundles;
880     NSArray *reqClasses;
881     
882     reqClasses = [requires objectForKey:@"classes"];
883     bundles = [self _locateBundlesForClassInfos:[reqClasses objectEnumerator]];
884     if (requiredBundles == nil)
885       requiredBundles = [NSMutableArray arrayWithCapacity:16];
886     [requiredBundles addObjectsFromArray:bundles];
887   }
888   
889   /* load located bundles */
890   {
891     NSEnumerator *e;
892     
893     e = [requiredBundles objectEnumerator];
894     while ((requiredBundle = [e nextObject])) {
895       Class bundleMaster;
896       
897       if ((bundleMaster = [self loadBundle:requiredBundle]) == Nil) {
898         NSLog(@"ERROR: could not load bundle %@ (%@) required by bundle %@.",
899               [requiredBundle bundlePath], requiredBundle, [_bundle bundlePath]);
900         continue;
901       }
902     }
903   }
904
905   /* check whether versions of classes match */
906   {
907     NSEnumerator *e;
908     NSDictionary *i;
909
910     e = [[requires objectForKey:@"classes"] objectEnumerator];
911     while ((i = [e nextObject])) {
912       NSString *className;
913       Class clazz;
914
915       if ((className = [i objectForKey:@"name"]) == nil)
916         continue;
917
918       if ((clazz = NSClassFromString(className)) == Nil)
919         continue;
920       
921       if ([i objectForKey:@"exact-version"]) {
922         int v;
923
924         v = [[i objectForKey:@"exact-version"] intValue];
925         
926         if (v != [clazz version]) {
927           NSLog(@"ERROR: required exact class match failed:\n"
928                 @"  class:            %@\n"
929                 @"  required version: %i\n"
930                 @"  loaded version:   %i\n"
931                 @"  bundle:           %@",
932                 className,
933                 v, [clazz version],
934                 [_bundle bundlePath]);
935         }
936       }
937       else if ([i objectForKey:@"version"]) {
938         int v;
939         
940         v = [[i objectForKey:@"version"] intValue];
941         
942         if (v > [clazz version]) {
943           NSLog(@"ERROR: provided class does not match required version:\n"
944                 @"  class:                  %@\n"
945                 @"  least required version: %i\n"
946                 @"  loaded version:         %i\n"
947                 @"  bundle:                 %@",
948                 className,
949                 v, [clazz version],
950                 [_bundle bundlePath]);
951         }
952       }
953     }
954   }
955   
956   return YES;
957 }
958 - (BOOL)_postLoadBundle:(NSBundle *)_bundle info:(NSDictionary *)_bundleInfo {
959   return YES;
960 }
961
962 - (id)loadBundle:(NSBundle *)_bundle {
963   NSString     *path       = nil;
964   NSDictionary *bundleInfo = nil;
965   id bundleManager = nil;
966
967 #if DEBUG
968   NSAssert(self->loadedBundles, @"missing loadedBundles hashmap ..");
969 #endif
970   
971   if ((bundleManager = NSMapGet(self->loadedBundles, _bundle)))
972     return bundleManager;
973   
974   if (_bundle == [NSBundle mainBundle])
975     return [NSBundle mainBundle];
976   
977   if ([self->loadingBundles containsObject:_bundle])
978     // recursive call
979     return nil;
980   
981   if (self->loadingBundles == nil)
982     self->loadingBundles = [[NSMutableSet allocWithZone:[self zone]] init];
983   [self->loadingBundles addObject:_bundle];
984
985   path = [_bundle bundlePath];
986   path = [self makeBundleInfoPath:path];
987
988   if ((bundleInfo = NSMapGet(self->pathToBundleInfo, path)) == nil) {
989     if ([[NSFileManager defaultManager] fileExistsAtPath:path])
990       bundleInfo = [self _loadBundleInfoAtExistingPath:path];
991   }
992   
993   if (![self _preLoadBundle:_bundle info:bundleInfo])
994     goto done;
995   
996   if (![_bundle _loadForBundleManager:self])
997     goto done;
998   
999   if (![self _postLoadBundle:_bundle info:bundleInfo])
1000     goto done;
1001   
1002   if ((bundleManager = 
1003        [self _initializeLoadedBundle:_bundle info:bundleInfo])) {
1004     NSMapInsert(self->loadedBundles, _bundle, bundleManager);
1005     
1006     if ([bundleManager respondsToSelector:
1007                          @selector(bundleManager:didLoadBundle:)])
1008       [bundleManager bundleManager:self didLoadBundle:_bundle];
1009   }
1010 #if 0
1011   else {
1012     NSLog(@"ERROR(%s): couldn't initialize loaded bundle '%@'",
1013           __PRETTY_FUNCTION__, [_bundle bundlePath]);
1014   }
1015 #endif
1016  done:
1017   [self->loadingBundles removeObject:_bundle];
1018
1019   if (bundleManager) {
1020     if (bundleInfo == nil)
1021       bundleInfo = [NSDictionary dictionary];
1022     
1023     [[NSNotificationCenter defaultCenter]
1024                            postNotificationName:
1025                              NGBundleWasLoadedNotificationName
1026                            object:_bundle
1027                            userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
1028                              self,          @"NGBundleManager",
1029                              bundleManager, @"NGBundleHandler",
1030                              bundleInfo,    @"NGBundleInfo",
1031                              nil]];
1032   }
1033   return bundleManager;
1034 }
1035
1036 // manager
1037
1038 - (id)principalObjectOfBundle:(NSBundle *)_bundle {
1039   return (id)NSMapGet(self->loadedBundles, _bundle);
1040 }
1041
1042 // resources
1043
1044 static BOOL _doesInfoMatch(NSArray *keys, NSDictionary *dict, NSDictionary *info)
1045 {
1046   int i, count;
1047
1048   for (i = 0, count = [keys count]; i < count; i++) {
1049     NSString *key;
1050     id kv, vv;
1051
1052     key = [keys objectAtIndex:i];
1053     vv  = [info objectForKey:key];
1054
1055     if (vv == nil) {
1056       /* info has no matching key */
1057       return NO;
1058     }
1059
1060     kv = [dict objectForKey:key];
1061     if (![kv isEqual:vv])
1062       return NO;
1063   }
1064   return YES;
1065 }
1066
1067 - (NSDictionary *)configForResource:(id)_resource ofType:(NSString *)_type
1068   providedByBundle:(NSBundle *)_bundle
1069 {
1070   NSDictionary *bundleInfo = nil;
1071   NSString     *infoPath;
1072   NSEnumerator *providedResources;
1073   NSArray      *rnKeys = nil;
1074   int          rnKeyCount = 0;
1075   id           info;
1076
1077   if ([_resource respondsToSelector:@selector(objectForKey:)]) {
1078     rnKeys     = [_resource allKeys];
1079     rnKeyCount = [rnKeys count];
1080   }
1081   
1082   infoPath = [self makeBundleInfoPath:[_bundle bundlePath]];
1083
1084   /* check whether info is in cache */
1085   if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1086     if (![[NSFileManager defaultManager] fileExistsAtPath:infoPath])
1087       /* no bundle-info.plist available .. */
1088       return nil;
1089
1090     /* load info */
1091     bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1092   }
1093
1094   /* get provided resources config */
1095   
1096   providedResources =
1097     [[(NSDictionary *)[bundleInfo objectForKey:@"provides"] objectForKey:_type]
1098                   objectEnumerator];
1099   if (providedResources == nil) return nil;
1100
1101   /* scan provided resources */
1102   
1103   while ((info = [providedResources nextObject])) {
1104     if (rnKeys) {
1105       if (!_doesInfoMatch(rnKeys, _resource, info))
1106         continue;
1107     }
1108     else {
1109       NSString *name;
1110       
1111       name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1112       if (name == nil) continue;
1113       if (![name isEqualToString:_resource]) continue;
1114     }
1115     return info;
1116   }
1117   return nil;
1118 }
1119
1120 - (void)_processInfoForProvidedResources:(NSDictionary *)info 
1121   ofType:(NSString *)_type path:(NSString *)path
1122   resourceName:(NSString *)_resourceName
1123   resourceSelector:(NGBundleResourceSelector)_selector
1124   context:(void *)_context
1125   andAddToResultArray:(NSMutableArray *)result
1126 {
1127   NSEnumerator *providedResources = nil;
1128   if (info == nil) return;
1129   
1130   /* direct match (a bundle was specified in the path) */
1131
1132   providedResources = [[(NSDictionary *)[info objectForKey:@"provides"]
1133                               objectForKey:_type]
1134                               objectEnumerator];
1135   info = nil;
1136   if (providedResources == nil) return;
1137
1138   /* scan provide array */
1139   while ((info = [providedResources nextObject])) {
1140     NSString *name;
1141           
1142     if ((name = [[info objectForKey:@"name"] stringValue]) == nil)
1143       continue;
1144
1145     if (_resourceName) {
1146       if (![name isEqualToString:_resourceName])
1147         continue;
1148     }
1149     if (_selector) {
1150       if (!_selector(_resourceName, _type, path, info, self, _context))
1151         continue;
1152     }
1153     
1154     [result addObject:path];
1155   }
1156 }
1157
1158 - (NSArray *)pathsForBundlesProvidingResource:(NSString *)_resourceName
1159   ofType:(NSString *)_type
1160   resourceSelector:(NGBundleResourceSelector)_selector
1161   context:(void *)_context
1162 {
1163   /* TODO: split up method */
1164   NSMutableArray *result = nil;
1165   NSFileManager  *fm;
1166   NSEnumerator   *e;
1167   NSString       *path;
1168
1169   if (debugOn) {
1170     NSLog(@"BM LOOKUP pathes (%d bundles loaded): %@ / %@", 
1171           NSCountMapTable(self->loadedBundles), _resourceName, _type);
1172   }
1173   
1174   fm     = [NSFileManager defaultManager];
1175   result = [NSMutableArray arrayWithCapacity:64];
1176   
1177   // TODO: look in loaded bundles
1178   
1179   /* check physical pathes */
1180   
1181   e = [self->bundleSearchPaths objectEnumerator];
1182   while ((path = [e nextObject]) != nil) {
1183     NSEnumerator *dir;
1184     BOOL     isDir = NO;
1185     NSString *tmp, *bundleDirPath;
1186     id       info = nil;
1187     
1188     if (![fm fileExistsAtPath:path isDirectory:&isDir])
1189       continue;
1190       
1191     if (!isDir) continue;
1192       
1193     /* check whether an appropriate bundle is contained in 'path' */
1194         
1195     dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1196     while ((bundleDirPath = [dir nextObject]) != nil) {
1197       NSDictionary *bundleInfo      = nil;
1198       NSEnumerator *providedResources = nil;
1199       NSString     *infoPath;
1200       id           info;
1201           
1202       bundleDirPath = [path stringByAppendingPathComponent:bundleDirPath];
1203       infoPath = [self makeBundleInfoPath:bundleDirPath];
1204           
1205       // TODO: can we use _doesBundleInfo:path:providedResource:... ?
1206       if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath))==nil) {
1207         if (![fm fileExistsAtPath:infoPath])
1208           continue;
1209
1210         bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1211       }
1212       
1213       providedResources = 
1214         [[(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1215                                       objectForKey:_type]
1216                                       objectEnumerator];
1217       if (providedResources == nil) continue;
1218       
1219       /* scan 'provides' array */
1220       while ((info = [providedResources nextObject])) {
1221         NSString *name;
1222             
1223         name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1224         if (name == nil) continue;
1225         
1226         if (_resourceName != nil) {
1227           if (![name isEqualToString:_resourceName])
1228             continue;
1229         }
1230         if (_selector != NULL) {
1231           if (!_selector(name, _type, bundleDirPath, info, self, _context))
1232             continue;
1233         }
1234
1235         [result addObject:bundleDirPath];
1236         break;
1237       }
1238     }
1239     
1240     /* check for direct match (NGBundlePath element is a bundle) */
1241     
1242     tmp = [self makeBundleInfoPath:path];
1243
1244     if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1245       if ([fm fileExistsAtPath:tmp])
1246         info = [self _loadBundleInfoAtExistingPath:tmp];
1247     }
1248     
1249     [self _processInfoForProvidedResources:info ofType:_type path:path
1250           resourceName:_resourceName resourceSelector:_selector
1251           context:_context
1252           andAddToResultArray:result];
1253   }
1254   
1255   if ([result count] == 0) {
1256     [self logWithFormat:
1257             @"Note(%s): method does not search in loaded bundles for "
1258             @"resources of type '%@'",
1259             __PRETTY_FUNCTION__, _type];
1260   }
1261   
1262   return [[result copy] autorelease];
1263 }
1264
1265 - (BOOL)_doesBundleInfo:(NSDictionary *)_bundleInfo path:(NSString *)_path
1266   provideResource:(id)_resourceName ofType:(NSString *)_type
1267   rnKeys:(NSArray *)_rnKeys
1268   resourceSelector:(NGBundleResourceSelector)_selector context:(void *)_context
1269 {
1270   NSEnumerator *providedResources;
1271   NSDictionary *info;
1272   
1273   providedResources = 
1274     [[(NSDictionary *)[_bundleInfo objectForKey:@"provides"] 
1275                       objectForKey:_type] objectEnumerator];
1276   if (providedResources == nil) return NO;
1277   
1278   /* scan provide array */
1279   while ((info = [providedResources nextObject])) {
1280     if (_rnKeys != nil) {
1281       if (!_doesInfoMatch(_rnKeys, _resourceName, info))
1282         continue;
1283     }
1284     else {
1285       NSString *name;
1286
1287       name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1288       if (name == nil) continue;
1289       if (![name isEqualToString:_resourceName]) continue;
1290     }
1291     
1292     if (_selector != NULL) {
1293       if (!_selector(_resourceName, _type, _path, info, self, _context))
1294         continue;
1295     }
1296     
1297     /* all conditions applied (found) */
1298     return YES;
1299   }
1300   return NO;
1301 }
1302
1303 - (NSString *)pathOfLoadedBundleProvidingResource:(id)_resourceName
1304   ofType:(NSString *)_type
1305   resourceSelector:(NGBundleResourceSelector)_selector context:(void *)_context
1306 {
1307   NSMapEnumerator menum;
1308   NSString     *path;
1309   NSDictionary *bundleInfo;
1310   NSArray      *rnKeys;
1311   
1312   rnKeys = ([_resourceName respondsToSelector:@selector(objectForKey:)])
1313     ? [_resourceName allKeys]
1314     : nil;
1315   
1316   menum = NSEnumerateMapTable(self->pathToBundleInfo);
1317   while (NSNextMapEnumeratorPair(&menum, (void *)&path, (void *)&bundleInfo)) {
1318     if (debugOn) {
1319       NSLog(@"check loaded bundle for resource %@: %@", _resourceName,
1320             path);
1321     }
1322     
1323     if ([self _doesBundleInfo:bundleInfo path:path
1324               provideResource:_resourceName ofType:_type rnKeys:rnKeys
1325               resourceSelector:_selector context:_context])
1326       /* strip bundle-info.plist name */
1327       return [path stringByDeletingLastPathComponent];
1328   }
1329   
1330   return nil;
1331 }
1332
1333 - (NSString *)pathForBundleProvidingResource:(id)_resourceName
1334   ofType:(NSString *)_type
1335   resourceSelector:(NGBundleResourceSelector)_selector
1336   context:(void *)_context
1337 {
1338   /* main path lookup method */
1339   // TODO: this method seriously needs some refactoring
1340   NSFileManager *fm;
1341   NSEnumerator  *e;
1342   NSString      *path;
1343   NSArray       *rnKeys = nil;
1344   int           rnKeyCount = 0;
1345   
1346   if (debugOn) {
1347     NSLog(@"BM LOOKUP path (%d bundles loaded): %@ / %@", 
1348           NSCountMapTable(self->loadedBundles), _resourceName, _type);
1349   }
1350   
1351   /* look in loaded bundles */
1352   
1353   path = [self pathOfLoadedBundleProvidingResource:_resourceName ofType:_type
1354                resourceSelector:_selector context:_context];
1355   if (path != nil) return path;
1356   
1357   /* look in filesystem */
1358   
1359   if ([_resourceName respondsToSelector:@selector(objectForKey:)]) {
1360     rnKeys     = [_resourceName allKeys];
1361     rnKeyCount = [rnKeys count];
1362   }
1363   
1364   fm = [NSFileManager defaultManager];
1365   e = [self->bundleSearchPaths objectEnumerator];
1366   while ((path = [e nextObject]) != nil) {
1367     NSEnumerator *dir;
1368     BOOL     isDir = NO;
1369     NSString *tmp;
1370     id       info = nil;
1371     
1372     if (![fm fileExistsAtPath:path isDirectory:&isDir])
1373       continue;
1374     
1375     if (!isDir) continue;
1376     
1377     /* check whether an appropriate bundle is contained in 'path' */
1378         
1379     dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1380     while ((tmp = [dir nextObject])) {
1381       NSDictionary *bundleInfo      = nil;
1382       NSString     *infoPath;
1383       
1384       tmp      = [path stringByAppendingPathComponent:tmp];
1385       infoPath = [self makeBundleInfoPath:tmp];
1386       
1387       if (debugOn)
1388         NSLog(@"check path path=%@ info=%@", tmp, infoPath);
1389           
1390       if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1391         if (![fm fileExistsAtPath:infoPath])
1392           continue;
1393
1394         bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1395       }
1396       if (debugOn)
1397         NSLog(@"found info for path=%@ info=%@: %@", tmp,infoPath,bundleInfo);
1398       
1399       if ([self _doesBundleInfo:bundleInfo path:tmp
1400                 provideResource:_resourceName ofType:_type rnKeys:rnKeys
1401                 resourceSelector:_selector context:_context])
1402         return tmp;
1403     }
1404     
1405     /* check for direct match */
1406       
1407     tmp = [self makeBundleInfoPath:path];
1408
1409     if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1410         if ([fm fileExistsAtPath:tmp])
1411           info = [self _loadBundleInfoAtExistingPath:tmp];
1412         else if (debugOn) {
1413           NSLog(@"WARNING(%s): did not find direct path '%@'",
1414                 __PRETTY_FUNCTION__, tmp);
1415         }
1416     }
1417       
1418     if (info != nil) {
1419         // direct match (a bundle was specified in the path)
1420         NSEnumerator *providedResources;
1421         NSDictionary *provides;
1422         
1423         provides          = [(NSDictionary *)info objectForKey:@"provides"];
1424         providedResources = [[provides objectForKey:_type] objectEnumerator];
1425         info              = nil;
1426         if (providedResources == nil) continue;
1427         
1428         // scan provide array
1429         while ((info = [providedResources nextObject])) {
1430           if (rnKeys) {
1431             if (!_doesInfoMatch(rnKeys, _resourceName, info))
1432               continue;
1433           }
1434           else {
1435             NSString *name;
1436
1437             name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1438             if (name == nil) continue;
1439             if (![name isEqualToString:_resourceName]) continue;
1440           }
1441
1442           if (_selector) {
1443             if (!_selector(_resourceName, _type, tmp, info, self, _context))
1444               continue;
1445           }
1446           /* all conditions applied */
1447           return tmp;
1448         }
1449     }
1450   }
1451   return nil;
1452 }
1453
1454 - (NSBundle *)bundleProvidingResource:(id)_name ofType:(NSString *)_type {
1455   NSString *bp;
1456   
1457   if (debugOn) NSLog(@"BM LOOKUP: %@ / %@", _name, _type);
1458   
1459   bp = [self pathForBundleProvidingResource:_name
1460              ofType:_type
1461              resourceSelector:NULL context:nil];
1462   if ([bp length] == 0) {
1463 #if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) && HEAVY_DEBUG
1464     NSLog(@"%s: found no resource '%@' of type '%@' ...",
1465           __PRETTY_FUNCTION__, _resourceName, _resourceType);
1466 #endif
1467     if (debugOn) NSLog(@"  did not find: %@ / %@", _name, _type);
1468     return nil;
1469   }
1470   
1471   if (debugOn) NSLog(@"  FOUND: %@", bp);
1472   return [self bundleWithPath:bp];
1473 }
1474
1475 - (NSArray *)bundlesProvidingResource:(id)_resourceName
1476   ofType:(NSString *)_type
1477 {
1478   NSArray        *paths;
1479   NSMutableArray *bundles;
1480   int i, count;
1481
1482   paths = [self pathsForBundlesProvidingResource:_resourceName
1483                 ofType:_type
1484                 resourceSelector:NULL context:nil];
1485   
1486   count = [paths count];
1487   if (paths == nil) return nil;
1488   if (count == 0)   return paths;
1489
1490   bundles = [NSMutableArray arrayWithCapacity:count];
1491   for (i = 0; i < count; i++) {
1492     NSBundle *bundle;
1493
1494     if ((bundle = [self bundleWithPath:[paths objectAtIndex:i]]))
1495       [bundles addObject:bundle];
1496   }
1497   return [[bundles copy] autorelease];
1498 }
1499
1500 - (NSArray *)providedResourcesOfType:(NSString *)_resourceType
1501   inBundle:(NSBundle *)_bundle
1502 {
1503   NSString     *path;
1504   NSDictionary *bundleInfo;
1505   
1506   path = [self makeBundleInfoPath:[_bundle bundlePath]];
1507   if (path == nil) return nil;
1508   
1509   /* retrieve bundle info dictionary */
1510   if ((bundleInfo = NSMapGet(self->pathToBundleInfo, path)) == nil)
1511     bundleInfo = [self _loadBundleInfoAtExistingPath:path];
1512   
1513   return [(NSDictionary *)[bundleInfo objectForKey:@"provides"] 
1514                                       objectForKey:_resourceType];
1515 }
1516
1517 - (void)_addRegisteredProvidedResourcesOfType:(NSString *)_type
1518   toSet:(NSMutableSet *)_result
1519 {
1520   NSMapEnumerator menum;
1521   NSString     *path;
1522   NSDictionary *bundleInfo;
1523   
1524   menum = NSEnumerateMapTable(self->pathToBundleInfo);
1525   while (NSNextMapEnumeratorPair(&menum, (void *)&path, (void *)&bundleInfo)) {
1526     NSArray *providedResources;
1527     
1528     if (debugOn)
1529       NSLog(@"check loaded bundle for resource types %@: %@", _type, path);
1530     
1531     providedResources = 
1532       [(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1533                        objectForKey:_type];
1534     if (providedResources == nil) continue;
1535
1536     [_result addObjectsFromArray:providedResources];
1537   }
1538 }
1539
1540 - (NSArray *)providedResourcesOfType:(NSString *)_resourceType {
1541   NSMutableSet  *result = nil;
1542   NSFileManager *fm = [NSFileManager defaultManager];
1543   NSEnumerator  *e;
1544   NSString      *path;
1545   
1546   result = [NSMutableSet setWithCapacity:128];
1547   
1548   /* scan loaded bundles */
1549   
1550   [self _addRegisteredProvidedResourcesOfType:_resourceType toSet:result];
1551   
1552   /* scan all bundle search paths */
1553   
1554   e = [self->bundleSearchPaths objectEnumerator];
1555   while ((path = [e nextObject]) != nil) {
1556     NSEnumerator *dir;
1557     BOOL     isDir = NO;
1558     NSString *tmp;
1559     id       info = nil;
1560
1561     if (![fm fileExistsAtPath:path isDirectory:&isDir])
1562       continue;
1563     if (!isDir) continue;
1564
1565     /* check whether an appropriate bundle is contained in 'path' */
1566     
1567     // TODO: move to own method
1568     dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1569     while ((tmp = [dir nextObject]) != nil) {
1570       NSDictionary *bundleInfo      = nil;
1571       NSArray      *providedResources = nil;
1572       NSString     *infoPath;
1573           
1574       tmp = [path stringByAppendingPathComponent:tmp];
1575       infoPath = [self makeBundleInfoPath:tmp];
1576       
1577 #if 0
1578       NSLog(@"  info path: %@", tmp);
1579 #endif
1580
1581       if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1582         if (![fm fileExistsAtPath:infoPath])
1583           continue;
1584
1585         bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1586       }
1587
1588       providedResources = 
1589         [(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1590                          objectForKey:_resourceType];
1591       if (providedResources == nil) continue;
1592
1593       [result addObjectsFromArray:providedResources];
1594     }
1595     
1596     /* check for direct match */
1597       
1598     tmp = [self makeBundleInfoPath:path];
1599
1600     if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1601       if ([fm fileExistsAtPath:tmp])
1602         info = [self _loadBundleInfoAtExistingPath:tmp];
1603     }
1604       
1605     if (info != nil) {
1606       // direct match (a bundle was specified in the path)
1607       NSArray      *providedResources;
1608       NSDictionary *provides;
1609
1610       provides          = [(NSDictionary *)info objectForKey:@"provides"];
1611       providedResources = [provides objectForKey:_resourceType];
1612       info = nil;
1613       if (providedResources == nil) continue;
1614
1615       [result addObjectsFromArray:providedResources];
1616     }
1617   }
1618   return [result allObjects];
1619 }
1620
1621 - (NSBundle *)bundleProvidingResourceOfType:(NSString *)_resourceType
1622   matchingQualifier:(EOQualifier *)_qual
1623 {
1624   NSFileManager  *fm = [NSFileManager defaultManager];
1625   NSEnumerator   *e;
1626   NSString       *path;
1627
1628   /* foreach search path entry */
1629   
1630   e = [self->bundleSearchPaths objectEnumerator];
1631   while ((path = [e nextObject])) {
1632     BOOL isDir = NO;
1633     
1634     if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1635       NSString *tmp;
1636       id info = nil;
1637       if (!isDir) continue;
1638
1639       /* check whether an appropriate bundle is contained in 'path' */
1640       {
1641         NSEnumerator *dir;
1642
1643         dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1644         while ((tmp = [dir nextObject])) {
1645           NSDictionary *bundleInfo;
1646           NSArray      *providedResources;
1647           NSString     *infoPath;
1648           
1649           tmp      = [path stringByAppendingPathComponent:tmp];
1650           infoPath = [self makeBundleInfoPath:tmp];
1651           
1652           if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1653             if (![fm fileExistsAtPath:infoPath])
1654               continue;
1655
1656             bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1657           }
1658           
1659           bundleInfo        = [bundleInfo objectForKey:@"provides"];
1660           providedResources = [bundleInfo objectForKey:_resourceType];
1661           bundleInfo        = nil;
1662           if (providedResources == nil) continue;
1663
1664           providedResources =
1665             [providedResources filteredArrayUsingQualifier:_qual];
1666
1667           if ([providedResources count] > 0)
1668             return [self bundleWithPath:tmp];
1669         }
1670       }
1671
1672       /* check for direct match */
1673       
1674       tmp = [self makeBundleInfoPath:path];
1675
1676       if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1677         if ([fm fileExistsAtPath:tmp])
1678           info = [self _loadBundleInfoAtExistingPath:tmp];
1679       }
1680       
1681       if (info) {
1682         // direct match (a bundle was specified in the path)
1683         NSArray      *providedResources;
1684         NSDictionary *provides;
1685         
1686         provides          = [(NSDictionary *)info objectForKey:@"provides"];
1687         providedResources = [provides objectForKey:_resourceType];
1688         info = nil;
1689         if (providedResources == nil) continue;
1690
1691         providedResources =
1692           [providedResources filteredArrayUsingQualifier:_qual];
1693
1694         if ([providedResources count] > 0)
1695           return [self bundleWithPath:path];
1696       }
1697     }
1698   }
1699   return nil;
1700 }
1701
1702 - (NSBundle *)bundlesProvidingResourcesOfType:(NSString *)_resourceType
1703   matchingQualifier:(EOQualifier *)_qual
1704 {
1705   NSMutableArray *bundles = nil;
1706   NSFileManager  *fm = [NSFileManager defaultManager];
1707   NSEnumerator   *e;
1708   NSString       *path;
1709
1710   bundles = [NSMutableArray arrayWithCapacity:128];
1711
1712   /* foreach search path entry */
1713   
1714   e = [self->bundleSearchPaths objectEnumerator];
1715   while ((path = [e nextObject])) {
1716     BOOL isDir = NO;
1717     
1718     if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1719       NSString *tmp;
1720       id info = nil;
1721       if (!isDir) continue;
1722
1723       /* check whether an appropriate bundle is contained in 'path' */
1724       {
1725         NSEnumerator *dir;
1726
1727         dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1728         while ((tmp = [dir nextObject])) {
1729           NSDictionary *bundleInfo      = nil;
1730           NSArray      *providedResources = nil;
1731           NSString     *infoPath;
1732           
1733           tmp = [path stringByAppendingPathComponent:tmp];
1734           infoPath = [self makeBundleInfoPath:tmp];
1735           
1736           if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1737             if (![fm fileExistsAtPath:infoPath])
1738               continue;
1739
1740             bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1741           }
1742           
1743           bundleInfo        = [bundleInfo objectForKey:@"provides"];
1744           providedResources = [bundleInfo objectForKey:_resourceType];
1745           bundleInfo        = nil;
1746           if (providedResources == nil) continue;
1747
1748           providedResources =
1749             [providedResources filteredArrayUsingQualifier:_qual];
1750
1751           if ([providedResources count] > 0)
1752             [bundles addObject:[self bundleWithPath:tmp]];
1753         }
1754       }
1755
1756       /* check for direct match */
1757       
1758       tmp = [self makeBundleInfoPath:path];
1759
1760       if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1761         if ([fm fileExistsAtPath:tmp])
1762           info = [self _loadBundleInfoAtExistingPath:tmp];
1763       }
1764       
1765       if (info) {
1766         // direct match (a bundle was specified in the path)
1767         NSArray      *providedResources;
1768         NSDictionary *provides;
1769         
1770         provides          = [(NSDictionary *)info objectForKey:@"provides"];
1771         providedResources = [provides objectForKey:_resourceType];
1772         info = nil;
1773         if (providedResources == nil) continue;
1774
1775         providedResources =
1776           [providedResources filteredArrayUsingQualifier:_qual];
1777
1778         if ([providedResources count] > 0)
1779           [bundles addObject:[self bundleWithPath:path]];
1780       }
1781     }
1782   }
1783   return [[bundles copy] autorelease];
1784 }
1785
1786 /* notifications */
1787
1788 - (void)_bundleDidLoadNotifcation:(NSNotification *)_notification {
1789   NSDictionary *ui = [_notification userInfo];
1790
1791 #if 0
1792   NSLog(@"bundle %@ did load with classes %@",
1793         [[_notification object] bundlePath],
1794         [ui objectForKey:@"NSLoadedClasses"]);
1795 #endif
1796   
1797   [self registerBundle:[_notification object]
1798         classes:[ui objectForKey:@"NSLoadedClasses"]
1799         categories:[ui objectForKey:@"NSLoadedCategories"]];
1800 }
1801
1802 /* debugging */
1803
1804 - (BOOL)isDebuggingEnabled {
1805   return debugOn;
1806 }
1807
1808 @end /* NGBundleManager */
1809
1810 @implementation NSBundle(BundleManagerSupport)
1811
1812 + (id)alloc {
1813   return [NGBundle alloc];
1814 }
1815 + (id)allocWithZone:(NSZone *)zone {
1816   return [NGBundle allocWithZone:zone];
1817 }
1818
1819 #if !(NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY)
1820 //#warning remember, bundleForClass is not overridden !
1821 #if 0
1822 + (NSBundle *)bundleForClass:(Class)aClass {
1823   return [[NGBundleManager defaultBundleManager] bundleForClass:aClass];
1824 }
1825 #endif
1826 + (NSBundle *)bundleWithPath:(NSString*)path {
1827   return [[NGBundleManager defaultBundleManager] bundleWithPath:path];
1828 }
1829 #endif
1830
1831 @end /* NSBundle(BundleManagerSupport) */
1832
1833 @implementation NSBundle(NGBundleManagerExtensions)
1834
1835 - (id)principalObject {
1836   return [[NGBundleManager defaultBundleManager]
1837                            principalObjectOfBundle:self];
1838 }
1839
1840 - (NSArray *)providedResourcesOfType:(NSString *)_resourceType {
1841   return [[NGBundleManager defaultBundleManager]
1842                            providedResourcesOfType:_resourceType
1843                            inBundle:self];
1844 }
1845
1846 - (NSString *)bundleName {
1847   return [[[self bundlePath] lastPathComponent] stringByDeletingPathExtension];
1848 }
1849
1850 - (NSString *)bundleType {
1851   return [[self bundlePath] pathExtension];
1852 }
1853
1854 - (NSArray *)providedClasses {
1855   return [[NGBundleManager defaultBundleManager] classesProvidedByBundle:self];
1856 }
1857
1858 - (NSArray *)requiredClasses {
1859   return [[NGBundleManager defaultBundleManager] classesRequiredByBundle:self];
1860 }
1861
1862 - (NSArray *)requiredBundles {
1863   return [[NGBundleManager defaultBundleManager] bundlesRequiredByBundle:self];
1864 }
1865
1866 - (NSDictionary *)configForResource:(id)_resource ofType:(NSString *)_type {
1867   return [[NGBundleManager defaultBundleManager]
1868                            configForResource:_resource ofType:_type
1869                            providedByBundle:self];
1870 }
1871
1872 // loading
1873
1874 - (BOOL)_loadForBundleManager:(NGBundleManager *)_manager {
1875   return [self load];
1876 }
1877
1878 @end /* NSBundle(NGBundleManagerExtensions) */
1879
1880 @implementation NSBundle(NGLanguageResourceExtensions)
1881
1882 static BOOL debugLanguageLookup = NO;
1883
1884 // locating resources
1885
1886 - (NSString *)pathForResource:(NSString *)_name ofType:(NSString *)_ext
1887   inDirectory:(NSString *)_directory
1888   languages:(NSArray *)_languages
1889 {
1890   NSFileManager *fm;
1891   NSString      *path = nil;
1892   int i, langCount;
1893   id (*objAtIdx)(id,SEL,int);
1894   
1895   if (debugLanguageLookup) {
1896     NSLog(@"LOOKUP(%s): %@ | %@ | %@ | %@", __PRETTY_FUNCTION__,
1897           _name, _ext, _directory, [_languages componentsJoinedByString:@","]);
1898   }
1899   
1900   path = [self bundlePath];
1901   if ([_directory isNotNull]) {
1902     // TODO: should we change that?
1903     path = [path stringByAppendingPathComponent:_directory];
1904   }
1905   else {
1906 #if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY)
1907     path = [path stringByAppendingPathComponent:@"Contents"];
1908 #endif
1909     path = [path stringByAppendingPathComponent:@"Resources"];
1910   }
1911   
1912   if (debugLanguageLookup) NSLog(@"  BASE: %@", path);
1913   
1914   fm   = [NSFileManager defaultManager];
1915   if (![fm fileExistsAtPath:path])
1916     return nil;
1917   
1918   if (_ext != nil) _name = [_name stringByAppendingPathExtension:_ext];
1919   
1920   langCount = [_languages count];
1921   objAtIdx = (langCount > 0)
1922     ? (void*)[_languages methodForSelector:@selector(objectAtIndex:)]
1923     : NULL;
1924
1925   for (i = 0; i < langCount; i++) {
1926     NSString *language;
1927     NSString *lpath;
1928
1929     language = objAtIdx
1930       ? objAtIdx(_languages, @selector(objectAtIndex:), i)
1931       : [_languages objectAtIndex:i];
1932
1933     language = [language stringByAppendingPathExtension:@"lproj"];
1934     lpath = [path stringByAppendingPathComponent:language];
1935     lpath = [lpath stringByAppendingPathComponent:_name];
1936
1937     if ([fm fileExistsAtPath:lpath])
1938       return lpath;
1939   }
1940   
1941   if (debugLanguageLookup) 
1942     NSLog(@"  no language matched, check base: %@", path);
1943
1944   /* now look into x.bundle/Resources/name.type */
1945   if ([fm fileExistsAtPath:[path stringByAppendingPathComponent:_name]])
1946     return [path stringByAppendingPathComponent:_name];
1947
1948   return nil;
1949 }
1950
1951 - (NSString *)pathForResource:(NSString *)_name ofType:(NSString *)_ext
1952   languages:(NSArray *)_languages
1953 {
1954   NSString *path;
1955
1956   path = [self pathForResource:_name ofType:_ext
1957                inDirectory:@"Resources"
1958                languages:_languages];
1959   if (path) return path;
1960
1961   path = [self pathForResource:_name ofType:_ext
1962                inDirectory:nil
1963                languages:_languages];
1964   return path;
1965 }
1966
1967 @end /* NSBundle(NGLanguageResourceExtensions) */
1968
1969 @implementation NGBundle
1970
1971 + (id)alloc {
1972   return [self allocWithZone:NULL];
1973 }
1974 + (id)allocWithZone:(NSZone*)zone {
1975   return NSAllocateObject(self, 0, zone);
1976 }
1977
1978 - (id)initWithPath:(NSString *)__path {
1979   return [super initWithPath:__path];
1980 }
1981
1982 /* loading */
1983
1984 - (BOOL)_loadForBundleManager:(NGBundleManager *)_manager {
1985   return [super load];
1986 }
1987
1988 - (BOOL)load {
1989   NGBundleManager *bm;
1990
1991   bm = [NGBundleManager defaultBundleManager];
1992   
1993   return [bm loadBundle:self] ? YES : NO;
1994 }
1995
1996 + (NSBundle *)bundleForClass:(Class)aClass {
1997   return [[NGBundleManager defaultBundleManager] bundleForClass:aClass];
1998 }
1999 + (NSBundle *)bundleWithPath:(NSString*)path {
2000   return [[NGBundleManager defaultBundleManager] bundleWithPath:path];
2001 }
2002
2003 #if GNUSTEP_BASE_LIBRARY
2004
2005 - (Class)principalClass {
2006   Class c;
2007   NSString *cname;
2008   
2009   if ((c = [super principalClass]))
2010     return c;
2011   
2012   if ((cname = [[self infoDictionary] objectForKey:@"NSPrincipalClass"]) ==nil)
2013     return Nil;
2014   
2015   if ((c = NSClassFromString(cname)))
2016     return c;
2017   
2018   NSLog(@"%s: did not find principal class named '%@' of bundle %@",
2019         __PRETTY_FUNCTION__, cname, self);
2020   return Nil;
2021 }
2022
2023 /* description */
2024
2025 - (NSString *)description {
2026   char buffer[1024];
2027   
2028   sprintf (buffer,
2029            "<%s %p fullPath: %s infoDictionary: %p loaded=%s>",
2030            (char*)object_get_class_name(self),
2031            self,
2032            [[self bundlePath] cString],
2033            [self infoDictionary], 
2034            self->_codeLoaded ? "yes" : "no");
2035   
2036   return [NSString stringWithCString:buffer];
2037 }
2038 #endif
2039
2040 @end /* NGBundle */