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