]> err.no Git - sope/blob - sope-core/NGExtensions/NGBundleManager.m
added -isNotEmpty to NSData
[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%08X: %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%08X: %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(@"couldn't 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   requires = [_bundleInfo objectForKey:@"requires"];
900   
901   if (requires == nil)
902     /* invalid bundle info specified */
903     return YES;
904
905   /* load required bundles */
906   {
907     NSEnumerator *e;
908     NSDictionary *i;
909
910     /* locate required bundles */
911     
912     e = [[requires objectForKey:@"bundles"] objectEnumerator];
913     while ((i = [e nextObject]) != nil) {
914       NSString *bundleName;
915       
916       if (![i respondsToSelector:@selector(objectForKey:)]) {
917         NSLog(@"ERROR(%s): invalid bundle-info of bundle %@ !!!\n"
918               @"  requires-entry is not a dictionary: %@",
919               __PRETTY_FUNCTION__, _bundle, i);
920         continue;
921       }
922       
923       if ((bundleName = [i objectForKey:@"name"])) {
924         NSString *type;
925         
926         type = [i objectForKey:@"type"];
927         if (type == nil) type = @"bundle";
928
929         if ((requiredBundle = [self bundleWithName:bundleName type:type])) {
930           if (requiredBundles == nil)
931             requiredBundles = [NSMutableArray arrayWithCapacity:16];
932           
933           [requiredBundles addObject:requiredBundle];
934         }
935         else {
936           NSLog(@"ERROR(NGBundleManager): did not find bundle '%@' (type=%@) "
937                 @"required by bundle %@.",
938                 bundleName, type, [_bundle bundlePath]);
939           continue;
940         }
941       }
942       else
943         NSLog(@"ERROR: error in bundle-info.plist of bundle %@", _bundle);
944     }
945   }
946   
947   /* load located bundles */
948   {
949     NSEnumerator *e;
950     
951     e = [requiredBundles objectEnumerator];
952     while ((requiredBundle = [e nextObject]) != nil) {
953       Class bundleMaster;
954       
955       if ((bundleMaster = [self loadBundle:requiredBundle]) == Nil) {
956         NSLog(@"ERROR: could not load bundle %@ (%@) required by bundle %@.",
957               [requiredBundle bundlePath], requiredBundle,
958               [_bundle bundlePath]);
959         continue;
960       }
961     }
962   }
963
964   /* load required classes */
965   {
966     NSArray *bundles;
967     NSArray *reqClasses;
968     
969     reqClasses = [requires objectForKey:@"classes"];
970     
971     bundles = [self _locateBundlesForClassInfos:[reqClasses objectEnumerator]];
972     if (requiredBundles == nil)
973       requiredBundles = [NSMutableArray arrayWithCapacity:16];
974     [requiredBundles addObjectsFromArray:bundles];
975   }
976
977   /* load located bundles */
978   {
979     NSEnumerator *e;
980     
981     e = [requiredBundles objectEnumerator];
982     while ((requiredBundle = [e nextObject]) != nil) {
983       Class bundleMaster;
984       
985       if ((bundleMaster = [self loadBundle:requiredBundle]) == Nil) {
986         NSLog(@"ERROR: could not load bundle %@ (%@) required by bundle %@.",
987               [requiredBundle bundlePath], requiredBundle,
988               [_bundle bundlePath]);
989         continue;
990       }
991     }
992   }
993
994   /* check whether versions of classes match */
995   {
996     NSEnumerator *e;
997     NSDictionary *i;
998
999     e = [[requires objectForKey:@"classes"] objectEnumerator];
1000     while ((i = [e nextObject]) != nil) {
1001       NSString *className;
1002       Class clazz;
1003
1004       if ((className = [i objectForKey:@"name"]) == nil)
1005         continue;
1006
1007       if ((clazz = NSClassFromString(className)) == Nil)
1008         continue;
1009       
1010       if ([i objectForKey:@"exact-version"]) {
1011         int v;
1012
1013         v = [[i objectForKey:@"exact-version"] intValue];
1014         
1015         if (v != [clazz version]) {
1016           NSLog(@"ERROR: required exact class match failed:\n"
1017                 @"  class:            %@\n"
1018                 @"  required version: %i\n"
1019                 @"  loaded version:   %i\n"
1020                 @"  bundle:           %@",
1021                 className,
1022                 v, [clazz version],
1023                 [_bundle bundlePath]);
1024         }
1025       }
1026       else if ([i objectForKey:@"version"]) {
1027         int v;
1028         
1029         v = [[i objectForKey:@"version"] intValue];
1030         
1031         if (v > [clazz version]) {
1032           NSLog(@"ERROR: provided class does not match required version:\n"
1033                 @"  class:                  %@\n"
1034                 @"  least required version: %i\n"
1035                 @"  loaded version:         %i\n"
1036                 @"  bundle:                 %@",
1037                 className,
1038                 v, [clazz version],
1039                 [_bundle bundlePath]);
1040         }
1041       }
1042     }
1043   }
1044   
1045   return YES;
1046 }
1047 - (BOOL)_postLoadBundle:(NSBundle *)_bundle info:(NSDictionary *)_bundleInfo {
1048   return YES;
1049 }
1050
1051 - (id)loadBundle:(NSBundle *)_bundle {
1052   NSString     *path       = nil;
1053   NSDictionary *bundleInfo = nil;
1054   id bundleManager = nil;
1055
1056 #if DEBUG
1057   NSAssert(self->loadedBundles, @"missing loadedBundles hashmap ..");
1058 #endif
1059   
1060   if ((bundleManager = NSMapGet(self->loadedBundles, _bundle)))
1061     return bundleManager;
1062   
1063   if (_bundle == [NSBundle mainBundle])
1064     return [NSBundle mainBundle];
1065   
1066   if ([self->loadingBundles containsObject:_bundle])
1067     // recursive call
1068     return nil;
1069   
1070   if (self->loadingBundles == nil)
1071     self->loadingBundles = [[NSMutableSet allocWithZone:[self zone]] init];
1072   [self->loadingBundles addObject:_bundle];
1073
1074   path = [_bundle bundlePath];
1075   path = [self makeBundleInfoPath:path];
1076
1077   if ((bundleInfo = NSMapGet(self->pathToBundleInfo, path)) == nil) {
1078     if ([[NSFileManager defaultManager] fileExistsAtPath:path])
1079       bundleInfo = [self _loadBundleInfoAtExistingPath:path];
1080   }
1081   
1082   if (![self _preLoadBundle:_bundle info:bundleInfo])
1083     goto done;
1084   
1085   if (![_bundle _loadForBundleManager:self])
1086     goto done;
1087   
1088   if (![self _postLoadBundle:_bundle info:bundleInfo])
1089     goto done;
1090   
1091   if ((bundleManager = 
1092        [self _initializeLoadedBundle:_bundle info:bundleInfo])) {
1093     NSMapInsert(self->loadedBundles, _bundle, bundleManager);
1094     
1095     if ([bundleManager respondsToSelector:
1096                          @selector(bundleManager:didLoadBundle:)])
1097       [bundleManager bundleManager:self didLoadBundle:_bundle];
1098   }
1099 #if 0
1100   else {
1101     NSLog(@"ERROR(%s): couldn't initialize loaded bundle '%@'",
1102           __PRETTY_FUNCTION__, [_bundle bundlePath]);
1103   }
1104 #endif
1105  done:
1106   [self->loadingBundles removeObject:_bundle];
1107
1108   if (bundleManager) {
1109     if (bundleInfo == nil)
1110       bundleInfo = [NSDictionary dictionary];
1111     
1112     [[NSNotificationCenter defaultCenter]
1113                            postNotificationName:
1114                              NGBundleWasLoadedNotificationName
1115                            object:_bundle
1116                            userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
1117                              self,          @"NGBundleManager",
1118                              bundleManager, @"NGBundleHandler",
1119                              bundleInfo,    @"NGBundleInfo",
1120                              nil]];
1121   }
1122   return bundleManager;
1123 }
1124
1125 // manager
1126
1127 - (id)principalObjectOfBundle:(NSBundle *)_bundle {
1128   return (id)NSMapGet(self->loadedBundles, _bundle);
1129 }
1130
1131 // resources
1132
1133 static BOOL _doesInfoMatch(NSArray *keys, NSDictionary *dict, NSDictionary *info)
1134 {
1135   int i, count;
1136
1137   for (i = 0, count = [keys count]; i < count; i++) {
1138     NSString *key;
1139     id kv, vv;
1140
1141     key = [keys objectAtIndex:i];
1142     vv  = [info objectForKey:key];
1143
1144     if (vv == nil) {
1145       /* info has no matching key */
1146       return NO;
1147     }
1148
1149     kv = [dict objectForKey:key];
1150     if (![kv isEqual:vv])
1151       return NO;
1152   }
1153   return YES;
1154 }
1155
1156 - (NSDictionary *)configForResource:(id)_resource ofType:(NSString *)_type
1157   providedByBundle:(NSBundle *)_bundle
1158 {
1159   NSDictionary *bundleInfo = nil;
1160   NSString     *infoPath;
1161   NSEnumerator *providedResources;
1162   NSArray      *rnKeys = nil;
1163   int          rnKeyCount = 0;
1164   id           info;
1165
1166   if ([_resource respondsToSelector:@selector(objectForKey:)]) {
1167     rnKeys     = [_resource allKeys];
1168     rnKeyCount = [rnKeys count];
1169   }
1170   
1171   infoPath = [self makeBundleInfoPath:[_bundle bundlePath]];
1172
1173   /* check whether info is in cache */
1174   if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1175     if (![[NSFileManager defaultManager] fileExistsAtPath:infoPath])
1176       /* no bundle-info.plist available .. */
1177       return nil;
1178
1179     /* load info */
1180     bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1181   }
1182
1183   /* get provided resources config */
1184   
1185   providedResources =
1186     [[(NSDictionary *)[bundleInfo objectForKey:@"provides"] objectForKey:_type]
1187                   objectEnumerator];
1188   if (providedResources == nil) return nil;
1189
1190   /* scan provided resources */
1191   
1192   while ((info = [providedResources nextObject])) {
1193     if (rnKeys) {
1194       if (!_doesInfoMatch(rnKeys, _resource, info))
1195         continue;
1196     }
1197     else {
1198       NSString *name;
1199       
1200       name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1201       if (name == nil) continue;
1202       if (![name isEqualToString:_resource]) continue;
1203     }
1204     return info;
1205   }
1206   return nil;
1207 }
1208
1209 - (void)_processInfoForProvidedResources:(NSDictionary *)info 
1210   ofType:(NSString *)_type path:(NSString *)path
1211   resourceName:(NSString *)_resourceName
1212   resourceSelector:(NGBundleResourceSelector)_selector
1213   context:(void *)_context
1214   andAddToResultArray:(NSMutableArray *)result
1215 {
1216   NSEnumerator *providedResources = nil;
1217   if (info == nil) return;
1218   
1219   /* direct match (a bundle was specified in the path) */
1220
1221   providedResources = [[(NSDictionary *)[info objectForKey:@"provides"]
1222                               objectForKey:_type]
1223                               objectEnumerator];
1224   info = nil;
1225   if (providedResources == nil) return;
1226
1227   /* scan provide array */
1228   while ((info = [providedResources nextObject])) {
1229     NSString *name;
1230           
1231     if ((name = [[info objectForKey:@"name"] stringValue]) == nil)
1232       continue;
1233
1234     if (_resourceName) {
1235       if (![name isEqualToString:_resourceName])
1236         continue;
1237     }
1238     if (_selector) {
1239       if (!_selector(_resourceName, _type, path, info, self, _context))
1240         continue;
1241     }
1242     
1243     [result addObject:path];
1244   }
1245 }
1246
1247 - (NSArray *)pathsForBundlesProvidingResource:(NSString *)_resourceName
1248   ofType:(NSString *)_type
1249   resourceSelector:(NGBundleResourceSelector)_selector
1250   context:(void *)_context
1251 {
1252   /* TODO: split up method */
1253   NSMutableArray *result = nil;
1254   NSFileManager  *fm;
1255   NSEnumerator   *e;
1256   NSString       *path;
1257
1258   if (debugOn) {
1259     NSLog(@"BM LOOKUP pathes (%d bundles loaded): %@ / %@", 
1260           NSCountMapTable(self->loadedBundles), _resourceName, _type);
1261   }
1262   
1263   fm     = [NSFileManager defaultManager];
1264   result = [NSMutableArray arrayWithCapacity:64];
1265   
1266   // TODO: look in loaded bundles
1267   
1268   /* check physical pathes */
1269   
1270   e = [self->bundleSearchPaths objectEnumerator];
1271   while ((path = [e nextObject]) != nil) {
1272     NSEnumerator *dir;
1273     BOOL     isDir = NO;
1274     NSString *tmp, *bundleDirPath;
1275     id       info = nil;
1276     
1277     if (![fm fileExistsAtPath:path isDirectory:&isDir])
1278       continue;
1279       
1280     if (!isDir) continue;
1281       
1282     /* check whether an appropriate bundle is contained in 'path' */
1283         
1284     dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1285     while ((bundleDirPath = [dir nextObject]) != nil) {
1286       NSDictionary *bundleInfo      = nil;
1287       NSEnumerator *providedResources = nil;
1288       NSString     *infoPath;
1289       id           info;
1290           
1291       bundleDirPath = [path stringByAppendingPathComponent:bundleDirPath];
1292       infoPath = [self makeBundleInfoPath:bundleDirPath];
1293           
1294       // TODO: can we use _doesBundleInfo:path:providedResource:... ?
1295       if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath))==nil) {
1296         if (![fm fileExistsAtPath:infoPath])
1297           continue;
1298
1299         bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1300       }
1301       
1302       providedResources = 
1303         [[(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1304                                       objectForKey:_type]
1305                                       objectEnumerator];
1306       if (providedResources == nil) continue;
1307       
1308       /* scan 'provides' array */
1309       while ((info = [providedResources nextObject])) {
1310         NSString *name;
1311             
1312         name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1313         if (name == nil) continue;
1314         
1315         if (_resourceName != nil) {
1316           if (![name isEqualToString:_resourceName])
1317             continue;
1318         }
1319         if (_selector != NULL) {
1320           if (!_selector(name, _type, bundleDirPath, info, self, _context))
1321             continue;
1322         }
1323
1324         [result addObject:bundleDirPath];
1325         break;
1326       }
1327     }
1328     
1329     /* check for direct match (NGBundlePath element is a bundle) */
1330     
1331     tmp = [self makeBundleInfoPath:path];
1332
1333     if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1334       if ([fm fileExistsAtPath:tmp])
1335         info = [self _loadBundleInfoAtExistingPath:tmp];
1336     }
1337     
1338     [self _processInfoForProvidedResources:info ofType:_type path:path
1339           resourceName:_resourceName resourceSelector:_selector
1340           context:_context
1341           andAddToResultArray:result];
1342   }
1343   
1344   if ([result count] == 0) {
1345     [self logWithFormat:
1346             @"Note(%s): method does not search in loaded bundles for "
1347             @"resources of type '%@'",
1348             __PRETTY_FUNCTION__, _type];
1349   }
1350   
1351   return [[result copy] autorelease];
1352 }
1353
1354 - (BOOL)_doesBundleInfo:(NSDictionary *)_bundleInfo path:(NSString *)_path
1355   provideResource:(id)_resourceName ofType:(NSString *)_type
1356   rnKeys:(NSArray *)_rnKeys
1357   resourceSelector:(NGBundleResourceSelector)_selector context:(void *)_context
1358 {
1359   NSEnumerator *providedResources;
1360   NSDictionary *info;
1361   
1362   providedResources = 
1363     [[(NSDictionary *)[_bundleInfo objectForKey:@"provides"] 
1364                       objectForKey:_type] objectEnumerator];
1365   if (providedResources == nil) return NO;
1366   
1367   /* scan provide array */
1368   while ((info = [providedResources nextObject])) {
1369     if (_rnKeys != nil) {
1370       if (!_doesInfoMatch(_rnKeys, _resourceName, info))
1371         continue;
1372     }
1373     else {
1374       NSString *name;
1375
1376       name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1377       if (name == nil) continue;
1378       if (![name isEqualToString:_resourceName]) continue;
1379     }
1380     
1381     if (_selector != NULL) {
1382       if (!_selector(_resourceName, _type, _path, info, self, _context))
1383         continue;
1384     }
1385     
1386     /* all conditions applied (found) */
1387     return YES;
1388   }
1389   return NO;
1390 }
1391
1392 - (NSString *)pathOfLoadedBundleProvidingResource:(id)_resourceName
1393   ofType:(NSString *)_type
1394   resourceSelector:(NGBundleResourceSelector)_selector context:(void *)_context
1395 {
1396   NSMapEnumerator menum;
1397   NSString     *path;
1398   NSDictionary *bundleInfo;
1399   NSArray      *rnKeys;
1400   
1401   rnKeys = ([_resourceName respondsToSelector:@selector(objectForKey:)])
1402     ? [_resourceName allKeys]
1403     : nil;
1404   
1405   menum = NSEnumerateMapTable(self->pathToBundleInfo);
1406   while (NSNextMapEnumeratorPair(&menum, (void *)&path, (void *)&bundleInfo)) {
1407     if (debugOn) {
1408       NSLog(@"check loaded bundle for resource %@: %@", _resourceName,
1409             path);
1410     }
1411     
1412     if ([self _doesBundleInfo:bundleInfo path:path
1413               provideResource:_resourceName ofType:_type rnKeys:rnKeys
1414               resourceSelector:_selector context:_context])
1415       /* strip bundle-info.plist name */
1416       return [path stringByDeletingLastPathComponent];
1417   }
1418   
1419   return nil;
1420 }
1421
1422 - (NSString *)pathForBundleProvidingResource:(id)_resourceName
1423   ofType:(NSString *)_type
1424   resourceSelector:(NGBundleResourceSelector)_selector
1425   context:(void *)_context
1426 {
1427   /* main path lookup method */
1428   // TODO: this method seriously needs some refactoring
1429   NSFileManager *fm;
1430   NSEnumerator  *e;
1431   NSString      *path;
1432   NSArray       *rnKeys = nil;
1433   int           rnKeyCount = 0;
1434   
1435   if (debugOn) {
1436     NSLog(@"BM LOOKUP path (%d bundles loaded): %@ / %@", 
1437           NSCountMapTable(self->loadedBundles), _resourceName, _type);
1438   }
1439   
1440   /* look in loaded bundles */
1441   
1442   path = [self pathOfLoadedBundleProvidingResource:_resourceName ofType:_type
1443                resourceSelector:_selector context:_context];
1444   if (path != nil) return path;
1445   
1446   /* look in filesystem */
1447   
1448   if ([_resourceName respondsToSelector:@selector(objectForKey:)]) {
1449     rnKeys     = [_resourceName allKeys];
1450     rnKeyCount = [rnKeys count];
1451   }
1452   
1453   fm = [NSFileManager defaultManager];
1454   e = [self->bundleSearchPaths objectEnumerator];
1455   while ((path = [e nextObject]) != nil) {
1456     NSEnumerator *dir;
1457     BOOL     isDir = NO;
1458     NSString *tmp;
1459     id       info = nil;
1460     
1461     if (![fm fileExistsAtPath:path isDirectory:&isDir])
1462       continue;
1463     
1464     if (!isDir) continue;
1465     
1466     /* check whether an appropriate bundle is contained in 'path' */
1467         
1468     dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1469     while ((tmp = [dir nextObject]) != nil) {
1470       NSDictionary *bundleInfo      = nil;
1471       NSString     *infoPath;
1472       
1473       tmp      = [path stringByAppendingPathComponent:tmp];
1474       infoPath = [self makeBundleInfoPath:tmp];
1475       
1476       if (debugOn)
1477         NSLog(@"check path path=%@ info=%@", tmp, infoPath);
1478           
1479       if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1480         if (![fm fileExistsAtPath:infoPath])
1481           continue;
1482
1483         bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1484       }
1485       if (debugOn)
1486         NSLog(@"found info for path=%@ info=%@: %@", tmp,infoPath,bundleInfo);
1487       
1488       if ([self _doesBundleInfo:bundleInfo path:tmp
1489                 provideResource:_resourceName ofType:_type rnKeys:rnKeys
1490                 resourceSelector:_selector context:_context])
1491         return tmp;
1492     }
1493     
1494     /* check for direct match */
1495       
1496     tmp = [self makeBundleInfoPath:path];
1497
1498     if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1499         if ([fm fileExistsAtPath:tmp])
1500           info = [self _loadBundleInfoAtExistingPath:tmp];
1501         else if (debugOn) {
1502           NSLog(@"WARNING(%s): did not find direct path '%@'",
1503                 __PRETTY_FUNCTION__, tmp);
1504         }
1505     }
1506       
1507     if (info != nil) {
1508         // direct match (a bundle was specified in the path)
1509         NSEnumerator *providedResources;
1510         NSDictionary *provides;
1511         
1512         provides          = [(NSDictionary *)info objectForKey:@"provides"];
1513         providedResources = [[provides objectForKey:_type] objectEnumerator];
1514         info              = nil;
1515         if (providedResources == nil) continue;
1516         
1517         // scan provide array
1518         while ((info = [providedResources nextObject])) {
1519           if (rnKeys) {
1520             if (!_doesInfoMatch(rnKeys, _resourceName, info))
1521               continue;
1522           }
1523           else {
1524             NSString *name;
1525
1526             name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1527             if (name == nil) continue;
1528             if (![name isEqualToString:_resourceName]) continue;
1529           }
1530
1531           if (_selector) {
1532             if (!_selector(_resourceName, _type, tmp, info, self, _context))
1533               continue;
1534           }
1535           /* all conditions applied */
1536           return tmp;
1537         }
1538     }
1539   }
1540   return nil;
1541 }
1542
1543 - (NSBundle *)bundleProvidingResource:(id)_name ofType:(NSString *)_type {
1544   NSString *bp;
1545   
1546   if (debugOn) NSLog(@"BM LOOKUP: %@ / %@", _name, _type);
1547   
1548   bp = [self pathForBundleProvidingResource:_name
1549              ofType:_type
1550              resourceSelector:NULL context:nil];
1551   if ([bp length] == 0) {
1552 #if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) && HEAVY_DEBUG
1553     NSLog(@"%s: found no resource '%@' of type '%@' ...",
1554           __PRETTY_FUNCTION__, _resourceName, _resourceType);
1555 #endif
1556     if (debugOn) NSLog(@"  did not find: %@ / %@", _name, _type);
1557     return nil;
1558   }
1559   
1560   if (debugOn) NSLog(@"  FOUND: %@", bp);
1561   return [self bundleWithPath:bp];
1562 }
1563
1564 - (NSArray *)bundlesProvidingResource:(id)_resourceName
1565   ofType:(NSString *)_type
1566 {
1567   NSArray        *paths;
1568   NSMutableArray *bundles;
1569   int i, count;
1570
1571   paths = [self pathsForBundlesProvidingResource:_resourceName
1572                 ofType:_type
1573                 resourceSelector:NULL context:nil];
1574   
1575   count = [paths count];
1576   if (paths == nil) return nil;
1577   if (count == 0)   return paths;
1578
1579   bundles = [NSMutableArray arrayWithCapacity:count];
1580   for (i = 0; i < count; i++) {
1581     NSBundle *bundle;
1582
1583     if ((bundle = [self bundleWithPath:[paths objectAtIndex:i]]))
1584       [bundles addObject:bundle];
1585   }
1586   return [[bundles copy] autorelease];
1587 }
1588
1589 - (NSArray *)providedResourcesOfType:(NSString *)_resourceType
1590   inBundle:(NSBundle *)_bundle
1591 {
1592   NSString     *path;
1593   NSDictionary *bundleInfo;
1594   
1595   path = [self makeBundleInfoPath:[_bundle bundlePath]];
1596   if (path == nil) return nil;
1597   
1598   /* retrieve bundle info dictionary */
1599   if ((bundleInfo = NSMapGet(self->pathToBundleInfo, path)) == nil)
1600     bundleInfo = [self _loadBundleInfoAtExistingPath:path];
1601   
1602   return [(NSDictionary *)[bundleInfo objectForKey:@"provides"] 
1603                                       objectForKey:_resourceType];
1604 }
1605
1606 - (void)_addRegisteredProvidedResourcesOfType:(NSString *)_type
1607   toSet:(NSMutableSet *)_result
1608 {
1609   NSMapEnumerator menum;
1610   NSString     *path;
1611   NSDictionary *bundleInfo;
1612   
1613   menum = NSEnumerateMapTable(self->pathToBundleInfo);
1614   while (NSNextMapEnumeratorPair(&menum, (void *)&path, (void *)&bundleInfo)) {
1615     NSArray *providedResources;
1616     
1617     if (debugOn)
1618       NSLog(@"check loaded bundle for resource types %@: %@", _type, path);
1619     
1620     providedResources = 
1621       [(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1622                        objectForKey:_type];
1623     if (providedResources == nil) continue;
1624
1625     [_result addObjectsFromArray:providedResources];
1626   }
1627 }
1628
1629 - (NSArray *)providedResourcesOfType:(NSString *)_resourceType {
1630   NSMutableSet  *result = nil;
1631   NSFileManager *fm = [NSFileManager defaultManager];
1632   NSEnumerator  *e;
1633   NSString      *path;
1634   
1635   result = [NSMutableSet setWithCapacity:128];
1636   
1637   /* scan loaded bundles */
1638   
1639   [self _addRegisteredProvidedResourcesOfType:_resourceType toSet:result];
1640   
1641   /* scan all bundle search paths */
1642   
1643   e = [self->bundleSearchPaths objectEnumerator];
1644   while ((path = [e nextObject]) != nil) {
1645     NSEnumerator *dir;
1646     BOOL     isDir = NO;
1647     NSString *tmp;
1648     id       info = nil;
1649
1650     if (![fm fileExistsAtPath:path isDirectory:&isDir])
1651       continue;
1652     if (!isDir) continue;
1653
1654     /* check whether an appropriate bundle is contained in 'path' */
1655     
1656     // TODO: move to own method
1657     dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1658     while ((tmp = [dir nextObject]) != nil) {
1659       NSDictionary *bundleInfo      = nil;
1660       NSArray      *providedResources = nil;
1661       NSString     *infoPath;
1662           
1663       tmp = [path stringByAppendingPathComponent:tmp];
1664       infoPath = [self makeBundleInfoPath:tmp];
1665       
1666 #if 0
1667       NSLog(@"  info path: %@", tmp);
1668 #endif
1669
1670       if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1671         if (![fm fileExistsAtPath:infoPath])
1672           continue;
1673
1674         bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1675       }
1676
1677       providedResources = 
1678         [(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1679                          objectForKey:_resourceType];
1680       if (providedResources == nil) continue;
1681
1682       [result addObjectsFromArray:providedResources];
1683     }
1684     
1685     /* check for direct match */
1686       
1687     tmp = [self makeBundleInfoPath:path];
1688
1689     if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1690       if ([fm fileExistsAtPath:tmp])
1691         info = [self _loadBundleInfoAtExistingPath:tmp];
1692     }
1693       
1694     if (info != nil) {
1695       // direct match (a bundle was specified in the path)
1696       NSArray      *providedResources;
1697       NSDictionary *provides;
1698
1699       provides          = [(NSDictionary *)info objectForKey:@"provides"];
1700       providedResources = [provides objectForKey:_resourceType];
1701       info = nil;
1702       if (providedResources == nil) continue;
1703
1704       [result addObjectsFromArray:providedResources];
1705     }
1706   }
1707   return [result allObjects];
1708 }
1709
1710 - (NSBundle *)bundleProvidingResourceOfType:(NSString *)_resourceType
1711   matchingQualifier:(EOQualifier *)_qual
1712 {
1713   NSFileManager  *fm = [NSFileManager defaultManager];
1714   NSEnumerator   *e;
1715   NSString       *path;
1716
1717   /* foreach search path entry */
1718   
1719   e = [self->bundleSearchPaths objectEnumerator];
1720   while ((path = [e nextObject])) {
1721     BOOL isDir = NO;
1722     
1723     if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1724       NSString *tmp;
1725       id info = nil;
1726       if (!isDir) continue;
1727
1728       /* check whether an appropriate bundle is contained in 'path' */
1729       {
1730         NSEnumerator *dir;
1731
1732         dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1733         while ((tmp = [dir nextObject])) {
1734           NSDictionary *bundleInfo;
1735           NSArray      *providedResources;
1736           NSString     *infoPath;
1737           
1738           tmp      = [path stringByAppendingPathComponent:tmp];
1739           infoPath = [self makeBundleInfoPath:tmp];
1740           
1741           if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1742             if (![fm fileExistsAtPath:infoPath])
1743               continue;
1744
1745             bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1746           }
1747           
1748           bundleInfo        = [bundleInfo objectForKey:@"provides"];
1749           providedResources = [bundleInfo objectForKey:_resourceType];
1750           bundleInfo        = nil;
1751           if (providedResources == nil) continue;
1752
1753           providedResources =
1754             [providedResources filteredArrayUsingQualifier:_qual];
1755
1756           if ([providedResources count] > 0)
1757             return [self bundleWithPath:tmp];
1758         }
1759       }
1760
1761       /* check for direct match */
1762       
1763       tmp = [self makeBundleInfoPath:path];
1764
1765       if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1766         if ([fm fileExistsAtPath:tmp])
1767           info = [self _loadBundleInfoAtExistingPath:tmp];
1768       }
1769       
1770       if (info) {
1771         // direct match (a bundle was specified in the path)
1772         NSArray      *providedResources;
1773         NSDictionary *provides;
1774         
1775         provides          = [(NSDictionary *)info objectForKey:@"provides"];
1776         providedResources = [provides objectForKey:_resourceType];
1777         info = nil;
1778         if (providedResources == nil) continue;
1779
1780         providedResources =
1781           [providedResources filteredArrayUsingQualifier:_qual];
1782
1783         if ([providedResources count] > 0)
1784           return [self bundleWithPath:path];
1785       }
1786     }
1787   }
1788   return nil;
1789 }
1790
1791 - (NSBundle *)bundlesProvidingResourcesOfType:(NSString *)_resourceType
1792   matchingQualifier:(EOQualifier *)_qual
1793 {
1794   NSMutableArray *bundles = nil;
1795   NSFileManager  *fm = [NSFileManager defaultManager];
1796   NSEnumerator   *e;
1797   NSString       *path;
1798
1799   bundles = [NSMutableArray arrayWithCapacity:128];
1800
1801   /* foreach search path entry */
1802   
1803   e = [self->bundleSearchPaths objectEnumerator];
1804   while ((path = [e nextObject])) {
1805     BOOL isDir = NO;
1806     
1807     if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1808       NSString *tmp;
1809       id info = nil;
1810       if (!isDir) continue;
1811
1812       /* check whether an appropriate bundle is contained in 'path' */
1813       {
1814         NSEnumerator *dir;
1815
1816         dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1817         while ((tmp = [dir nextObject])) {
1818           NSDictionary *bundleInfo      = nil;
1819           NSArray      *providedResources = nil;
1820           NSString     *infoPath;
1821           
1822           tmp = [path stringByAppendingPathComponent:tmp];
1823           infoPath = [self makeBundleInfoPath:tmp];
1824           
1825           if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1826             if (![fm fileExistsAtPath:infoPath])
1827               continue;
1828
1829             bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1830           }
1831           
1832           bundleInfo        = [bundleInfo objectForKey:@"provides"];
1833           providedResources = [bundleInfo objectForKey:_resourceType];
1834           bundleInfo        = nil;
1835           if (providedResources == nil) continue;
1836
1837           providedResources =
1838             [providedResources filteredArrayUsingQualifier:_qual];
1839
1840           if ([providedResources count] > 0)
1841             [bundles addObject:[self bundleWithPath:tmp]];
1842         }
1843       }
1844
1845       /* check for direct match */
1846       
1847       tmp = [self makeBundleInfoPath:path];
1848
1849       if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1850         if ([fm fileExistsAtPath:tmp])
1851           info = [self _loadBundleInfoAtExistingPath:tmp];
1852       }
1853       
1854       if (info) {
1855         // direct match (a bundle was specified in the path)
1856         NSArray      *providedResources;
1857         NSDictionary *provides;
1858         
1859         provides          = [(NSDictionary *)info objectForKey:@"provides"];
1860         providedResources = [provides objectForKey:_resourceType];
1861         info = nil;
1862         if (providedResources == nil) continue;
1863
1864         providedResources =
1865           [providedResources filteredArrayUsingQualifier:_qual];
1866
1867         if ([providedResources count] > 0)
1868           [bundles addObject:[self bundleWithPath:path]];
1869       }
1870     }
1871   }
1872   return [[bundles copy] autorelease];
1873 }
1874
1875 /* notifications */
1876
1877 - (void)_bundleDidLoadNotifcation:(NSNotification *)_notification {
1878   NSDictionary *ui = [_notification userInfo];
1879
1880 #if 0
1881   NSLog(@"bundle %@ did load with classes %@",
1882         [[_notification object] bundlePath],
1883         [ui objectForKey:@"NSLoadedClasses"]);
1884 #endif
1885   
1886   [self registerBundle:[_notification object]
1887         classes:[ui objectForKey:@"NSLoadedClasses"]
1888         categories:[ui objectForKey:@"NSLoadedCategories"]];
1889 }
1890
1891 /* debugging */
1892
1893 - (BOOL)isDebuggingEnabled {
1894   return debugOn;
1895 }
1896
1897 @end /* NGBundleManager */
1898
1899 @implementation NSBundle(BundleManagerSupport)
1900
1901 + (id)alloc {
1902   return [NGBundle alloc];
1903 }
1904 + (id)allocWithZone:(NSZone *)zone {
1905   return [NGBundle allocWithZone:zone];
1906 }
1907
1908 #if !(NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY)
1909 //#warning remember, bundleForClass is not overridden !
1910 #if 0
1911 + (NSBundle *)bundleForClass:(Class)aClass {
1912   return [[NGBundleManager defaultBundleManager] bundleForClass:aClass];
1913 }
1914 #endif
1915 + (NSBundle *)bundleWithPath:(NSString*)path {
1916   return [[NGBundleManager defaultBundleManager] bundleWithPath:path];
1917 }
1918 #endif
1919
1920 @end /* NSBundle(BundleManagerSupport) */
1921
1922 @implementation NSBundle(NGBundleManagerExtensions)
1923
1924 - (id)principalObject {
1925   return [[NGBundleManager defaultBundleManager]
1926                            principalObjectOfBundle:self];
1927 }
1928
1929 - (NSArray *)providedResourcesOfType:(NSString *)_resourceType {
1930   return [[NGBundleManager defaultBundleManager]
1931                            providedResourcesOfType:_resourceType
1932                            inBundle:self];
1933 }
1934
1935 - (NSString *)bundleName {
1936   return [[[self bundlePath] lastPathComponent] stringByDeletingPathExtension];
1937 }
1938
1939 - (NSString *)bundleType {
1940   return [[self bundlePath] pathExtension];
1941 }
1942
1943 - (NSArray *)providedClasses {
1944   return [[NGBundleManager defaultBundleManager] classesProvidedByBundle:self];
1945 }
1946
1947 - (NSArray *)requiredClasses {
1948   return [[NGBundleManager defaultBundleManager] classesRequiredByBundle:self];
1949 }
1950
1951 - (NSArray *)requiredBundles {
1952   return [[NGBundleManager defaultBundleManager] bundlesRequiredByBundle:self];
1953 }
1954
1955 - (NSDictionary *)configForResource:(id)_resource ofType:(NSString *)_type {
1956   return [[NGBundleManager defaultBundleManager]
1957                            configForResource:_resource ofType:_type
1958                            providedByBundle:self];
1959 }
1960
1961 // loading
1962
1963 - (BOOL)_loadForBundleManager:(NGBundleManager *)_manager {
1964   return [self load];
1965 }
1966
1967 @end /* NSBundle(NGBundleManagerExtensions) */
1968
1969 @implementation NSBundle(NGLanguageResourceExtensions)
1970
1971 static BOOL debugLanguageLookup = NO;
1972
1973 // locating resources
1974
1975 - (NSString *)pathForResource:(NSString *)_name ofType:(NSString *)_ext
1976   inDirectory:(NSString *)_directory
1977   languages:(NSArray *)_languages
1978 {
1979   NSFileManager *fm;
1980   NSString      *path = nil;
1981   int i, langCount;
1982   id (*objAtIdx)(id,SEL,int);
1983   
1984   if (debugLanguageLookup) {
1985     NSLog(@"LOOKUP(%s): %@ | %@ | %@ | %@", __PRETTY_FUNCTION__,
1986           _name, _ext, _directory, [_languages componentsJoinedByString:@","]);
1987   }
1988   
1989   path = [self bundlePath];
1990   if ([_directory isNotNull]) {
1991     // TODO: should we change that?
1992     path = [path stringByAppendingPathComponent:_directory];
1993   }
1994   else {
1995 #if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY)
1996     path = [path stringByAppendingPathComponent:@"Contents"];
1997 #endif
1998     path = [path stringByAppendingPathComponent:@"Resources"];
1999   }
2000   
2001   if (debugLanguageLookup) NSLog(@"  BASE: %@", path);
2002   
2003   fm   = [NSFileManager defaultManager];
2004   if (![fm fileExistsAtPath:path])
2005     return nil;
2006   
2007   if (_ext != nil) _name = [_name stringByAppendingPathExtension:_ext];
2008   
2009   langCount = [_languages count];
2010   objAtIdx = (langCount > 0)
2011     ? (void*)[_languages methodForSelector:@selector(objectAtIndex:)]
2012     : NULL;
2013
2014   for (i = 0; i < langCount; i++) {
2015     NSString *language;
2016     NSString *lpath;
2017
2018     language = objAtIdx
2019       ? objAtIdx(_languages, @selector(objectAtIndex:), i)
2020       : [_languages objectAtIndex:i];
2021
2022     language = [language stringByAppendingPathExtension:@"lproj"];
2023     lpath = [path stringByAppendingPathComponent:language];
2024     lpath = [lpath stringByAppendingPathComponent:_name];
2025
2026     if ([fm fileExistsAtPath:lpath])
2027       return lpath;
2028   }
2029   
2030   if (debugLanguageLookup) 
2031     NSLog(@"  no language matched, check base: %@", path);
2032
2033   /* now look into x.bundle/Resources/name.type */
2034   if ([fm fileExistsAtPath:[path stringByAppendingPathComponent:_name]])
2035     return [path stringByAppendingPathComponent:_name];
2036
2037   return nil;
2038 }
2039
2040 - (NSString *)pathForResource:(NSString *)_name ofType:(NSString *)_ext
2041   languages:(NSArray *)_languages
2042 {
2043   NSString *path;
2044
2045   path = [self pathForResource:_name ofType:_ext
2046                inDirectory:@"Resources"
2047                languages:_languages];
2048   if (path) return path;
2049
2050   path = [self pathForResource:_name ofType:_ext
2051                inDirectory:nil
2052                languages:_languages];
2053   return path;
2054 }
2055
2056 @end /* NSBundle(NGLanguageResourceExtensions) */
2057
2058 @implementation NGBundle
2059
2060 + (id)alloc {
2061   return [self allocWithZone:NULL];
2062 }
2063 + (id)allocWithZone:(NSZone*)zone {
2064   return NSAllocateObject(self, 0, zone);
2065 }
2066
2067 - (id)initWithPath:(NSString *)__path {
2068   return [super initWithPath:__path];
2069 }
2070
2071 /* loading */
2072
2073 - (BOOL)_loadForBundleManager:(NGBundleManager *)_manager {
2074   return [super load];
2075 }
2076
2077 - (BOOL)load {
2078   NGBundleManager *bm;
2079
2080   bm = [NGBundleManager defaultBundleManager];
2081   
2082   return [bm loadBundle:self] ? YES : NO;
2083 }
2084
2085 + (NSBundle *)bundleForClass:(Class)aClass {
2086   return [[NGBundleManager defaultBundleManager] bundleForClass:aClass];
2087 }
2088 + (NSBundle *)bundleWithPath:(NSString*)path {
2089   return [[NGBundleManager defaultBundleManager] bundleWithPath:path];
2090 }
2091
2092 #if GNUSTEP_BASE_LIBRARY
2093
2094 - (Class)principalClass {
2095   Class c;
2096   NSString *cname;
2097   
2098   if ((c = [super principalClass]))
2099     return c;
2100   
2101   if ((cname = [[self infoDictionary] objectForKey:@"NSPrincipalClass"]) ==nil)
2102     return Nil;
2103   
2104   if ((c = NSClassFromString(cname)))
2105     return c;
2106   
2107   NSLog(@"%s: did not find principal class named '%@' of bundle %@",
2108         __PRETTY_FUNCTION__, cname, self);
2109   return Nil;
2110 }
2111
2112 /* description */
2113
2114 - (NSString *)description {
2115   char buffer[1024];
2116   
2117   sprintf (buffer,
2118            "<%s %p fullPath: %s infoDictionary: %p loaded=%s>",
2119            (char*)object_get_class_name(self),
2120            self,
2121            [[self bundlePath] cString],
2122            [self infoDictionary], 
2123            self->_codeLoaded ? "yes" : "no");
2124   
2125   return [NSString stringWithCString:buffer];
2126 }
2127 #endif
2128
2129 @end /* NGBundle */