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