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