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