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