2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
21 // $Id: NGBundleManager.m 4 2004-08-20 17:04:31Z helge $
23 #include "NGBundleManager.h"
25 #import <Foundation/NSFileManager.h>
26 #import <EOControl/EOQualifier.h>
29 #if 0 && GNUSTEP_BASE_LIBRARY /* supported in repository since 19990916-2254-MET */
30 /* hack until GNUstep provides the necessary callbacks */
31 # define NSNonRetainedObjectMapValueCallBacks NSObjectMapValueCallBacks
34 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
35 # include <NGExtensions/NGPropertyListParser.h>
38 #if NeXT_RUNTIME || APPLE_RUNTIME
40 #include <objc/objc-runtime.h>
42 //OBJC_EXPORT void objc_setClassHandler(int (*)(const char *));
44 static BOOL debugClassHook = NO;
46 static int _getClassHook(const char *className) {
47 if (className == NULL) return 0;
50 printf("lookup class '%s'.\n", className);
52 if (objc_lookUpClass(className))
56 static NGBundleManager *manager = nil;
60 NSLog(@"%s: look for class %s", __PRETTY_FUNCTION__, className);
62 manager = [NGBundleManager defaultBundleManager];
64 bundle = [manager bundleForClassNamed:
65 [NSString stringWithCString:className]];
68 NSLog(@"%s: found bundle %@", __PRETTY_FUNCTION__,
72 if (![manager loadBundle:bundle]) {
74 "bundleManager couldn't load bundle for class '%s'.\n",
79 Class c = objc_lookUpClass(className);
80 NSLog(@"%s: loaded bundle %@ for className %s class %@",
82 bundle, className, c);
94 #include <objc/objc-api.h>
96 static Class (*oldClassLoadHook)(const char *_name) = NULL;
98 static inline BOOL _isValidClassName(const char *_name) {
101 if (_name == NULL) return NO;
103 for (len = 0; (len < 256) && (*_name != '\0'); len++, _name++) {
105 if (!isalnum((int)*_name))
109 return (len == 256) ? NO : YES;
112 static Class _classLoadHook(const char *_name) {
113 if (_isValidClassName(_name)) {
114 static NGBundleManager *manager = nil;
117 //NSLog(@"%s: look for class %s", __PRETTY_FUNCTION__, _name);
119 manager = [NGBundleManager defaultBundleManager];
121 bundle = [manager bundleForClassNamed:[NSString stringWithCString:_name]];
123 //NSLog(@"%s: found bundle %@", __PRETTY_FUNCTION__, [bundle bundlePath]);
125 if ([manager loadBundle:bundle]) {
129 hook = _objc_lookup_class;
130 _objc_lookup_class = NULL;
131 clazz = objc_lookup_class(_name);
132 _objc_lookup_class = hook;
134 if (clazz) return clazz;
138 return (oldClassLoadHook != NULL) ? oldClassLoadHook(_name) : Nil;
140 #endif // GNU_RUNTIME
142 NSString *NGBundleWasLoadedNotificationName = @"NGBundleWasLoadedNotification";
144 @interface NSBundle(NGBundleManagerPrivate)
145 - (BOOL)_loadForBundleManager:(NGBundleManager *)_manager;
148 @interface NGBundleManager(PrivateMethods)
150 - (void)registerBundle:(NSBundle *)_bundle
151 classes:(NSArray *)_classes
152 categories:(NSArray *)_categories;
154 - (NSString *)pathForBundleProvidingResource:(NSString *)_resourceName
155 ofType:(NSString *)_type
156 resourceSelector:(NGBundleResourceSelector)_selector
157 context:(void *)_ctx;
159 - (NSString *)makeBundleInfoPath:(NSString *)_path;
163 static BOOL _selectClassByVersion(NSString *_resourceName,
164 NSString *_resourceType,
166 NSDictionary *_resourceConfig,
167 NGBundleManager *_bundleManager,
173 if (![_resourceType isEqualToString:@"classes"])
176 if (_version == NULL)
178 if ([(id)_version intValue] == -1)
181 if ((tmp = [_resourceConfig objectForKey:@"version"])) {
182 classVersion = [tmp intValue];
184 if (classVersion < [(id)_version intValue]) {
185 NSLog(@"WARNING: class version mismatch for class %@: "
186 @"requested at least version %i, got version %i",
187 _resourceName, [(id)_version intValue], classVersion);
190 if ((tmp = [_resourceConfig objectForKey:@"exact-version"])) {
191 classVersion = [tmp intValue];
193 if (classVersion != [(id)_version intValue]) {
194 NSLog(@"WARNING: class version mismatch for class %@: "
195 @"requested exact version %i, got version %i",
196 _resourceName, [(id)_version intValue], classVersion);
202 @implementation NGBundleManager
205 static NGBundleManager *defaultManager = nil;
206 static BOOL debugOn = NO;
209 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
211 debugOn = [ud boolForKey:@"NGBundleManagerDebugEnabled"];
216 if (_objc_lookup_class != _classLoadHook) {
217 oldClassLoadHook = _objc_lookup_class;
218 _objc_lookup_class = _classLoadHook;
223 + (id)defaultBundleManager {
224 if (defaultManager == nil) {
225 defaultManager = [[NGBundleManager alloc] init];
227 #if NeXT_RUNTIME || APPLE_RUNTIME
229 static BOOL didRegisterCallback = NO;
231 if (!didRegisterCallback) {
232 didRegisterCallback = YES;
233 objc_setClassHandler(_getClassHook);
239 if (_objc_lookup_class != _classLoadHook) {
240 oldClassLoadHook = _objc_lookup_class;
241 _objc_lookup_class = _classLoadHook;
245 return defaultManager;
248 - (void)_setupBundleSearchPathes {
254 pi = [NSProcessInfo processInfo];
256 /* setup bundle search path */
258 self->bundleSearchPaths = [[NSMutableArray alloc] init];
260 // first add main-bundle path
262 path = [[[pi arguments] objectAtIndex:0] stringByDeletingLastPathComponent];
263 if ([path isEqual:@""])
267 /* The path is the complete path to the executable, including the
268 processor, the OS and the library combo. Strip these directories
269 from the main bundle's path. */
270 path = [[[path stringByDeletingLastPathComponent]
271 stringByDeletingLastPathComponent]
272 stringByDeletingLastPathComponent];
275 [self->bundleSearchPaths addObject:path];
277 // look into NGBundlePath default
279 if ((ud = [NSUserDefaults standardUserDefaults]) == nil) {
280 // got this with gstep-base during the port, apparently it happens
281 // if the bundle manager is created inside the setup process of
282 // gstep-base (for whatever reason)
283 NSLog(@"ERROR(NGBundleManager): got no system userdefaults object!");
289 paths = [ud arrayForKey:@"NGBundlePath"];
291 paths = [ud stringForKey:@"NGBundlePath"];
293 #if defined(__MINGW32__)
294 paths = [paths componentsSeparatedByString:@";"];
296 paths = [paths componentsSeparatedByString:@":"];
301 [self->bundleSearchPaths addObjectsFromArray:paths];
303 NSLog(@"Note: NGBundlePath default is not configured.");
305 /* look into environment */
307 paths = [[pi environment] objectForKey:@"NGBundlePath"];
309 #if defined(__MINGW32__)
310 paths = [paths componentsSeparatedByString:@";"];
312 paths = [paths componentsSeparatedByString:@":"];
315 if (paths) [self->bundleSearchPaths addObjectsFromArray:paths];
317 /* add standard bundle paths */
321 // TODO: whats that supposed to do?
322 // TODO: replace with proper path locator function!
328 env = [[NSProcessInfo processInfo] environment];
330 if ((tmp = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil)
331 tmp = [env objectForKey:@"GNUSTEP_PATHLIST"];
332 tmp = [tmp componentsSeparatedByString:@":"];
334 for (i = 0, count = [tmp count]; i < count; i++) {
335 p = [tmp objectAtIndex:i];
336 p = [p stringByAppendingPathComponent:@"Library"];
337 p = [p stringByAppendingPathComponent:@"Bundles"];
338 if ([self->bundleSearchPaths containsObject:p]) continue;
340 if (p) [self->bundleSearchPaths addObject:p];
345 #if DEBUG && NeXT_Foundation_LIBRARY && 0
346 NSLog(@"%s: bundle search pathes:\n%@", __PRETTY_FUNCTION__,
347 self->bundleSearchPaths);
351 - (void)_registerLoadedBundles {
352 NSEnumerator *currentBundles;
353 NSBundle *loadedBundle;
355 currentBundles = [[NSBundle allBundles] objectEnumerator];
356 while ((loadedBundle = [currentBundles nextObject]))
357 [self registerBundle:loadedBundle classes:nil categories:nil];
360 - (void)_registerForBundleLoadNotification {
361 [[NSNotificationCenter defaultCenter]
363 selector:@selector(_bundleDidLoadNotifcation:)
364 name:@"NSBundleDidLoadNotification"
369 #if GNUSTEP_BASE_LIBRARY
370 if ([NSUserDefaults standardUserDefaults] == nil) {
371 /* called inside setup process, deny creation (HACK) */
377 if ((self = [super init])) {
378 self->classToBundle =
379 NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
380 NSNonRetainedObjectMapValueCallBacks,
382 self->classNameToBundle =
383 NSCreateMapTable(NSObjectMapKeyCallBacks,
384 NSNonRetainedObjectMapValueCallBacks,
386 self->categoryNameToBundle =
387 NSCreateMapTable(NSObjectMapKeyCallBacks,
388 NSNonRetainedObjectMapValueCallBacks,
391 NSCreateMapTable(NSObjectMapKeyCallBacks,
392 NSNonRetainedObjectMapValueCallBacks,
394 self->pathToBundleInfo =
395 NSCreateMapTable(NSObjectMapKeyCallBacks,
396 NSObjectMapValueCallBacks,
399 NSCreateMapTable(NSObjectMapKeyCallBacks,
400 NSNonRetainedObjectMapValueCallBacks,
402 self->loadedBundles =
403 NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks,
404 NSObjectMapValueCallBacks,
407 [self _setupBundleSearchPathes];
408 [self _registerLoadedBundles];
409 [self _registerForBundleLoadNotification];
415 [self->loadingBundles release];
416 if (self->loadedBundles) NSFreeMapTable(self->loadedBundles);
417 if (self->classToBundle) NSFreeMapTable(self->classToBundle);
418 if (self->classNameToBundle) NSFreeMapTable(self->classNameToBundle);
419 if (self->categoryNameToBundle) NSFreeMapTable(self->categoryNameToBundle);
420 if (self->pathToBundle) NSFreeMapTable(self->pathToBundle);
421 if (self->pathToBundleInfo) NSFreeMapTable(self->pathToBundleInfo);
422 if (self->nameToBundle) NSFreeMapTable(self->nameToBundle);
423 [self->bundleSearchPaths release];
427 /* registering bundles */
429 - (void)registerBundle:(NSBundle *)_bundle
430 classes:(NSArray *)_classes
431 categories:(NSArray *)_categories
436 //NSLog(@"NGBundleManager: register loaded bundle %@", [_bundle bundlePath]);
438 e = [_classes objectEnumerator];
439 while ((v = [e nextObject])) {
440 NSMapInsert(self->classToBundle, NSClassFromString(v), _bundle);
441 NSMapInsert(self->classNameToBundle, v, _bundle);
444 e = [_categories objectEnumerator];
445 while ((v = [e nextObject]))
446 NSMapInsert(self->categoryNameToBundle, v, _bundle);
451 - (NSString *)pathForBundleWithName:(NSString *)_name type:(NSString *)_type {
452 NSFileManager *fm = [NSFileManager defaultManager];
455 NSString *bundlePath;
458 /* first check in table */
461 bundlePath = [_name stringByAppendingPathExtension:_type];
463 if ((bundle = NSMapGet(self->nameToBundle, bundlePath)))
464 return [bundle bundlePath];
466 e = [self->bundleSearchPaths objectEnumerator];
467 while ((path = [e nextObject])) {
470 if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
471 if (!isDir) continue;
473 if ([[path lastPathComponent] isEqualToString:bundlePath]) {
474 // direct match (a bundle was specified in the path)
480 tmp = [path stringByAppendingPathComponent:bundlePath];
481 if ([fm fileExistsAtPath:tmp isDirectory:&isDir]) {
492 /* getting bundles */
494 - (NSBundle *)bundleForClass:(Class)aClass {
495 /* this method never loads a dynamic bundle (since the class is set up) */
498 bundle = NSMapGet(self->classToBundle, aClass);
500 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
502 bundle = [NSBundle bundleForClass:aClass];
503 if (bundle == [NSBundle mainBundle])
509 if the class wasn't loaded from a bundle, it's *either* the main bundle
510 or a bundle loaded before NGExtension was loaded !!!
513 #if !LIB_FOUNDATION_LIBRARY && !GNUSTEP_BASE_LIBRARY
514 # warning incorrect behaviour if NGExtensions is dynamically loaded !
515 // TODO: can we do anything about this? Can we detect the situation and
516 // print a log instead of the compile warning?
518 bundle = [NSBundle mainBundle];
519 NSMapInsert(self->classToBundle, aClass, bundle);
520 NSMapInsert(self->classNameToBundle, NSStringFromClass(aClass), bundle);
524 - (NSBundle *)bundleWithPath:(NSString *)path {
525 NSBundle *bundle = nil;
528 path = [path stringByResolvingSymlinksInPath];
532 if (debugOn) NSLog(@"find bundle for path: '%@'", path);
533 bundle = NSMapGet(self->pathToBundle, path);
536 if (debugOn) NSLog(@" found: %@", bundle);
540 if ((bundle = [(NGBundle *)[NGBundle alloc] initWithPath:path]) == nil) {
541 NSLog(@"ERROR(%s): could not create bundle for path: '%@'",
542 __PRETTY_FUNCTION__, path);
546 bn = [[bundle bundleName]
547 stringByAppendingPathExtension:[bundle bundleType]],
549 NSMapInsert(self->pathToBundle, path, bundle);
550 NSMapInsert(self->nameToBundle, bn, bundle);
554 - (NSBundle *)bundleWithName:(NSString *)_name type:(NSString *)_type {
558 bn = [_name stringByAppendingPathExtension:_type];
559 bundle = NSMapGet(self->nameToBundle, bn);
562 bundle = [self bundleWithPath:[self pathForBundleWithName:_name type:_type]];
564 if (![[bundle bundleType] isEqualToString:_type])
569 - (NSBundle *)bundleWithName:(NSString *)_name {
570 return [self bundleWithName:_name type:@"bundle"];
573 - (NSBundle *)bundleForClassNamed:(NSString *)_className {
574 NSString *path = nil;
575 NSBundle *bundle = nil;
577 if (_className == nil)
580 /* first check in table */
582 if ((bundle = NSMapGet(self->classNameToBundle, _className)))
586 /* then look in runtime, reset load callback to avoid recursion */
593 loadCallback = _objc_lookup_class;
594 _objc_lookup_class = NULL;
595 clazz = NSClassFromString(_className);
596 _objc_lookup_class = loadCallback;
599 /* the class is already loaded */
600 bundle = [self bundleForClass:clazz];
601 NSMapInsert(self->classNameToBundle, _className, bundle);
607 path = [self pathForBundleProvidingResource:_className
609 resourceSelector:_selectClassByVersion
610 context:NULL /* version */];
612 path = [path stringByResolvingSymlinksInPath];
613 NSAssert(path, @"couldn't resolve symlinks in path ..");
619 if ((bundle = [self bundleWithPath:path]))
620 NSMapInsert(self->classNameToBundle, _className, bundle);
631 - (NSArray *)bundlesRequiredByBundle:(NSBundle *)_bundle {
632 [self doesNotRecognizeSelector:_cmd];
636 - (NSArray *)classesProvidedByBundle:(NSBundle *)_bundle {
637 [self doesNotRecognizeSelector:_cmd];
640 - (NSArray *)classesRequiredByBundle:(NSBundle *)_bundle {
641 [self doesNotRecognizeSelector:_cmd];
647 - (NSString *)makeBundleInfoPath:(NSString *)_path {
648 #if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) && !defined(GSWARN)
649 return [[[_path stringByAppendingPathComponent:@"Contents"]
650 stringByAppendingPathComponent:@"Resources"]
651 stringByAppendingPathComponent:@"bundle-info.plist"];
653 return [_path stringByAppendingPathComponent:@"bundle-info.plist"];
657 - (id)_initializeLoadedBundle:(NSBundle *)_bundle
658 info:(NSDictionary *)_bundleInfo
662 /* check whether a handler was specified */
664 if ((handler = [_bundleInfo objectForKey:@"bundleHandler"])) {
665 if ((handler = NSClassFromString(handler)) == nil) {
666 NSLog(@"ERROR: did not find handler class %@ of bundle %@.",
667 [_bundleInfo objectForKey:@"bundleHandler"], [_bundle bundlePath]);
668 handler = [_bundle principalClass];
671 handler = [handler alloc];
673 if ([handler respondsToSelector:@selector(initForBundle:bundleManager:)])
674 handler = [handler initForBundle:_bundle bundleManager:self];
676 handler = [handler init];
677 handler = [handler autorelease];
679 if (handler == nil) {
680 NSLog(@"ERROR: could not instantiate handler class %@ of bundle %@.",
681 [_bundleInfo objectForKey:@"bundleHandler"], [_bundle bundlePath]);
682 handler = [_bundle principalClass];
686 if ((handler = [_bundle principalClass]) == nil)
687 /* use NGBundle class as default bundle handler */
688 handler = [NGBundle class];
696 - (NSDictionary *)_loadBundleInfoAtExistingPath:(NSString *)_path {
697 NSDictionary *bundleInfo;
700 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
701 bundleInfo = NGParsePropertyListFromFile(_path);
703 bundleInfo = [NSDictionary dictionaryWithContentsOfFile:_path];
705 if (bundleInfo == nil) {
706 NSLog(@"couldn't load bundle-info at path '%@' !", _path);
710 /* check required bundle manager version */
711 info = [bundleInfo objectForKey:@"requires"];
712 if ((info = [(NSDictionary *)info objectForKey:@"bundleManagerVersion"])) {
713 if ([info intValue] > [[self class] version]) {
714 /* bundle manager version does not match ... */
718 NSMapInsert(self->pathToBundleInfo, _path, bundleInfo);
722 - (NSBundle *)_locateBundleForClassInfo:(NSDictionary *)_classInfo {
726 if (_classInfo == nil)
728 if ((className = [_classInfo objectForKey:@"name"]) == nil) {
729 NSLog(@"ERROR: missing classname in bundle-info.plist class section !");
733 if ((bundle = [self bundleForClassNamed:className]) == nil) {
734 #if 0 // class might be already loaded
735 NSLog(@"ERROR: did not find class %@ required by bundle %@.",
736 className, [_bundle bundlePath]);
742 - (NSArray *)_locateBundlesForClassInfos:(NSEnumerator *)_classInfos {
743 NSMutableArray *requiredBundles;
746 requiredBundles = [NSMutableArray arrayWithCapacity:16];
747 while ((i = [_classInfos nextObject])) {
750 if ((bundle = [self _locateBundleForClassInfo:i]) == nil)
753 [requiredBundles addObject:bundle];
755 return requiredBundles;
758 - (BOOL)_preLoadBundle:(NSBundle *)_bundle info:(NSDictionary *)_bundleInfo {
759 /* TODO: split up this huge method */
760 NSDictionary *requires = nil;
761 NSMutableArray *requiredBundles = nil;
762 NSBundle *requiredBundle = nil;
764 requires = [_bundleInfo objectForKey:@"requires"];
767 /* invalid bundle info specified */
770 /* load required bundles */
775 /* locate required bundles */
777 e = [[requires objectForKey:@"bundles"] objectEnumerator];
778 while ((i = [e nextObject])) {
779 NSString *bundleName;
781 if (![i respondsToSelector:@selector(objectForKey:)]) {
782 NSLog(@"ERROR(%s): invalid bundle-info of bundle %@ !!!\n"
783 @" requires-entry is not a dictionary: %@",
784 __PRETTY_FUNCTION__, _bundle, i);
788 if ((bundleName = [i objectForKey:@"name"])) {
791 type = [i objectForKey:@"type"];
792 if (type == nil) type = @"bundle";
794 if ((requiredBundle = [self bundleWithName:bundleName type:type])) {
795 if (requiredBundles == nil)
796 requiredBundles = [NSMutableArray arrayWithCapacity:16];
798 [requiredBundles addObject:requiredBundle];
801 NSLog(@"ERROR(NGBundleManager): did not find bundle '%@' (type=%@) "
802 @"required by bundle %@.",
803 bundleName, type, [_bundle bundlePath]);
808 NSLog(@"ERROR: error in bundle-info.plist of bundle %@", _bundle);
812 /* load located bundles */
816 e = [requiredBundles objectEnumerator];
817 while ((requiredBundle = [e nextObject])) {
820 if ((bundleMaster = [self loadBundle:requiredBundle]) == Nil) {
821 NSLog(@"ERROR: could not load bundle %@ (%@) required by bundle %@.",
822 [requiredBundle bundlePath], requiredBundle, [_bundle bundlePath]);
828 /* load required classes */
833 reqClasses = [requires objectForKey:@"classes"];
834 bundles = [self _locateBundlesForClassInfos:[reqClasses objectEnumerator]];
835 if (requiredBundles == nil)
836 requiredBundles = [NSMutableArray arrayWithCapacity:16];
837 [requiredBundles addObjectsFromArray:bundles];
840 /* load located bundles */
844 e = [requiredBundles objectEnumerator];
845 while ((requiredBundle = [e nextObject])) {
848 if ((bundleMaster = [self loadBundle:requiredBundle]) == Nil) {
849 NSLog(@"ERROR: could not load bundle %@ (%@) required by bundle %@.",
850 [requiredBundle bundlePath], requiredBundle, [_bundle bundlePath]);
856 /* check whether versions of classes match */
861 e = [[requires objectForKey:@"classes"] objectEnumerator];
862 while ((i = [e nextObject])) {
866 if ((className = [i objectForKey:@"name"]) == nil)
869 if ((clazz = NSClassFromString(className)) == Nil)
872 if ([i objectForKey:@"exact-version"]) {
875 v = [[i objectForKey:@"exact-version"] intValue];
877 if (v != [clazz version]) {
878 NSLog(@"ERROR: required exact class match failed:\n"
880 @" required version: %i\n"
881 @" loaded version: %i\n"
885 [_bundle bundlePath]);
888 else if ([i objectForKey:@"version"]) {
891 v = [[i objectForKey:@"version"] intValue];
893 if (v > [clazz version]) {
894 NSLog(@"ERROR: provided class does not match required version:\n"
896 @" least required version: %i\n"
897 @" loaded version: %i\n"
901 [_bundle bundlePath]);
909 - (BOOL)_postLoadBundle:(NSBundle *)_bundle info:(NSDictionary *)_bundleInfo {
913 - (id)loadBundle:(NSBundle *)_bundle {
914 NSString *path = nil;
915 NSDictionary *bundleInfo = nil;
916 id bundleManager = nil;
919 NSAssert(self->loadedBundles, @"missing loadedBundles hashmap ..");
922 if ((bundleManager = NSMapGet(self->loadedBundles, _bundle)))
923 return bundleManager;
925 if (_bundle == [NSBundle mainBundle])
926 return [NSBundle mainBundle];
928 if ([self->loadingBundles containsObject:_bundle])
932 if (self->loadingBundles == nil)
933 self->loadingBundles = [[NSMutableSet allocWithZone:[self zone]] init];
934 [self->loadingBundles addObject:_bundle];
936 path = [_bundle bundlePath];
937 path = [self makeBundleInfoPath:path];
939 if ((bundleInfo = NSMapGet(self->pathToBundleInfo, path)) == nil) {
940 if ([[NSFileManager defaultManager] fileExistsAtPath:path])
941 bundleInfo = [self _loadBundleInfoAtExistingPath:path];
944 if (![self _preLoadBundle:_bundle info:bundleInfo])
947 if (![_bundle _loadForBundleManager:self])
950 if (![self _postLoadBundle:_bundle info:bundleInfo])
954 [self _initializeLoadedBundle:_bundle info:bundleInfo])) {
955 NSMapInsert(self->loadedBundles, _bundle, bundleManager);
957 if ([bundleManager respondsToSelector:
958 @selector(bundleManager:didLoadBundle:)])
959 [bundleManager bundleManager:self didLoadBundle:_bundle];
963 NSLog(@"ERROR(%s): couldn't initialize loaded bundle '%@'",
964 __PRETTY_FUNCTION__, [_bundle bundlePath]);
968 [self->loadingBundles removeObject:_bundle];
971 if (bundleInfo == nil)
972 bundleInfo = [NSDictionary dictionary];
974 [[NSNotificationCenter defaultCenter]
975 postNotificationName:
976 NGBundleWasLoadedNotificationName
978 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
979 self, @"NGBundleManager",
980 bundleManager, @"NGBundleHandler",
981 bundleInfo, @"NGBundleInfo",
984 return bundleManager;
989 - (id)principalObjectOfBundle:(NSBundle *)_bundle {
990 return (id)NSMapGet(self->loadedBundles, _bundle);
995 static BOOL _doesInfoMatch(NSArray *keys, NSDictionary *dict, NSDictionary *info)
999 for (i = 0, count = [keys count]; i < count; i++) {
1003 key = [keys objectAtIndex:i];
1004 vv = [info objectForKey:key];
1007 /* info has no matching key */
1011 kv = [dict objectForKey:key];
1012 if (![kv isEqual:vv])
1018 - (NSDictionary *)configForResource:(id)_resource ofType:(NSString *)_type
1019 providedByBundle:(NSBundle *)_bundle
1021 NSDictionary *bundleInfo = nil;
1023 NSEnumerator *providedResources;
1024 NSArray *rnKeys = nil;
1028 if ([_resource respondsToSelector:@selector(objectForKey:)]) {
1029 rnKeys = [_resource allKeys];
1030 rnKeyCount = [rnKeys count];
1033 infoPath = [self makeBundleInfoPath:[_bundle bundlePath]];
1035 /* check whether info is in cache */
1036 if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1037 if (![[NSFileManager defaultManager] fileExistsAtPath:infoPath])
1038 /* no bundle-info.plist available .. */
1042 bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1045 /* get provided resources config */
1048 [[(NSDictionary *)[bundleInfo objectForKey:@"provides"] objectForKey:_type]
1050 if (providedResources == nil) return nil;
1052 /* scan provided resources */
1054 while ((info = [providedResources nextObject])) {
1056 if (!_doesInfoMatch(rnKeys, _resource, info))
1062 name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1063 if (name == nil) continue;
1064 if (![name isEqualToString:_resource]) continue;
1071 - (void)_processInfoForProvidedResources:(NSDictionary *)info
1072 ofType:(NSString *)_type path:(NSString *)path
1073 resourceName:(NSString *)_resourceName
1074 resourceSelector:(NGBundleResourceSelector)_selector
1075 context:(void *)_context
1076 andAddToResultArray:(NSMutableArray *)result
1078 NSEnumerator *providedResources = nil;
1079 if (info == nil) return;
1081 /* direct match (a bundle was specified in the path) */
1083 providedResources = [[(NSDictionary *)[info objectForKey:@"provides"]
1087 if (providedResources == nil) return;
1089 /* scan provide array */
1090 while ((info = [providedResources nextObject])) {
1093 if ((name = [[info objectForKey:@"name"] stringValue]) == nil)
1096 if (_resourceName) {
1097 if (![name isEqualToString:_resourceName])
1101 if (!_selector(_resourceName, _type, path, info, self, _context))
1105 [result addObject:path];
1109 - (NSArray *)pathsForBundlesProvidingResource:(NSString *)_resourceName
1110 ofType:(NSString *)_type
1111 resourceSelector:(NGBundleResourceSelector)_selector
1112 context:(void *)_context
1114 /* TODO: split up method */
1115 NSMutableArray *result = nil;
1120 fm = [NSFileManager defaultManager];
1121 result = [NSMutableArray arrayWithCapacity:64];
1123 e = [self->bundleSearchPaths objectEnumerator];
1124 while ((path = [e nextObject])) {
1127 if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1130 if (!isDir) continue;
1132 /* check whether an appropriate bundle is contained in 'path' */
1136 dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1137 while ((tmp = [dir nextObject])) {
1138 NSDictionary *bundleInfo = nil;
1139 NSEnumerator *providedResources = nil;
1143 tmp = [path stringByAppendingPathComponent:tmp];
1144 infoPath = [self makeBundleInfoPath:tmp];
1146 if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1147 if (![fm fileExistsAtPath:infoPath])
1150 bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1154 [[(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1157 if (providedResources == nil) continue;
1159 // scan provide array
1160 while ((info = [providedResources nextObject])) {
1163 name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1164 if (name == nil) continue;
1166 if (_resourceName) {
1167 if (![name isEqualToString:_resourceName])
1171 if (!_selector(name, _type, tmp, info, self, _context))
1175 [result addObject:tmp];
1181 /* check for direct match */
1183 tmp = [self makeBundleInfoPath:path];
1185 if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1186 if ([fm fileExistsAtPath:tmp])
1187 info = [self _loadBundleInfoAtExistingPath:tmp];
1190 [self _processInfoForProvidedResources:info ofType:_type path:path
1191 resourceName:_resourceName resourceSelector:_selector
1193 andAddToResultArray:result];
1196 return [[result copy] autorelease];
1199 - (NSString *)pathForBundleProvidingResource:(id)_resourceName
1200 ofType:(NSString *)_type
1201 resourceSelector:(NGBundleResourceSelector)_selector
1202 context:(void *)_context
1204 NSFileManager *fm = [NSFileManager defaultManager];
1207 NSArray *rnKeys = nil;
1210 if ([_resourceName respondsToSelector:@selector(objectForKey:)]) {
1211 rnKeys = [_resourceName allKeys];
1212 rnKeyCount = [rnKeys count];
1215 e = [self->bundleSearchPaths objectEnumerator];
1216 while ((path = [e nextObject])) {
1219 if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1223 if (!isDir) continue;
1225 /* check whether an appropriate bundle is contained in 'path' */
1229 dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1230 while ((tmp = [dir nextObject])) {
1231 NSDictionary *bundleInfo = nil;
1232 NSEnumerator *providedResources = nil;
1236 tmp = [path stringByAppendingPathComponent:tmp];
1237 infoPath = [self makeBundleInfoPath:tmp];
1240 NSLog(@"check path path=%@ info=%@", tmp, infoPath);
1242 if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1243 if (![fm fileExistsAtPath:infoPath])
1246 bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1249 NSLog(@"found info for path=%@ info=%@: %@",
1250 tmp, infoPath, bundleInfo);
1254 [[(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1257 if (providedResources == nil) continue;
1259 // scan provide array
1260 while ((info = [providedResources nextObject])) {
1262 if (!_doesInfoMatch(rnKeys, _resourceName, info))
1268 name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1269 if (name == nil) continue;
1270 if (![name isEqualToString:_resourceName]) continue;
1274 if (!_selector(_resourceName, _type, tmp, info, self, _context))
1277 /* all conditions applied */
1283 /* check for direct match */
1285 tmp = [self makeBundleInfoPath:path];
1287 if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1288 if ([fm fileExistsAtPath:tmp])
1289 info = [self _loadBundleInfoAtExistingPath:tmp];
1291 NSLog(@"WARNING(%s): did not find direct path '%@'",
1292 __PRETTY_FUNCTION__, tmp);
1297 // direct match (a bundle was specified in the path)
1298 NSEnumerator *providedResources;
1299 NSDictionary *provides;
1301 provides = [(NSDictionary *)info objectForKey:@"provides"];
1302 providedResources = [[provides objectForKey:_type] objectEnumerator];
1304 if (providedResources == nil) continue;
1306 // scan provide array
1307 while ((info = [providedResources nextObject])) {
1309 if (!_doesInfoMatch(rnKeys, _resourceName, info))
1315 name = [[(NSDictionary *)info objectForKey:@"name"] stringValue];
1316 if (name == nil) continue;
1317 if (![name isEqualToString:_resourceName]) continue;
1321 if (!_selector(_resourceName, _type, tmp, info, self, _context))
1324 /* all conditions applied */
1333 - (NSBundle *)bundleProvidingResource:(id)_resourceName
1334 ofType:(NSString *)_resourceType
1338 bp = [self pathForBundleProvidingResource:_resourceName
1339 ofType:_resourceType
1340 resourceSelector:NULL context:nil];
1341 if ([bp length] == 0) {
1342 #if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) && HEAVY_DEBUG
1343 NSLog(@"%s: found no resource '%@' of type '%@' ...",
1344 __PRETTY_FUNCTION__, _resourceName, _resourceType);
1349 return [self bundleWithPath:bp];
1352 - (NSArray *)bundlesProvidingResource:(id)_resourceName
1353 ofType:(NSString *)_type
1356 NSMutableArray *bundles;
1359 paths = [self pathsForBundlesProvidingResource:_resourceName
1361 resourceSelector:NULL context:nil];
1363 count = [paths count];
1364 if (paths == nil) return nil;
1365 if (count == 0) return paths;
1367 bundles = [NSMutableArray arrayWithCapacity:count];
1368 for (i = 0; i < count; i++) {
1371 if ((bundle = [self bundleWithPath:[paths objectAtIndex:i]]))
1372 [bundles addObject:bundle];
1374 return [[bundles copy] autorelease];
1377 - (NSArray *)providedResourcesOfType:(NSString *)_resourceType
1378 inBundle:(NSBundle *)_bundle
1381 NSDictionary *bundleInfo;
1383 path = [self makeBundleInfoPath:[_bundle bundlePath]];
1384 if (path == nil) return nil;
1386 /* retrieve bundle info dictionary */
1387 if ((bundleInfo = NSMapGet(self->pathToBundleInfo, path)) == nil)
1388 bundleInfo = [self _loadBundleInfoAtExistingPath:path];
1390 return [(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1391 objectForKey:_resourceType];
1394 - (NSArray *)providedResourcesOfType:(NSString *)_resourceType {
1395 NSMutableSet *result = nil;
1396 NSFileManager *fm = [NSFileManager defaultManager];
1400 result = [NSMutableSet setWithCapacity:128];
1402 e = [self->bundleSearchPaths objectEnumerator];
1403 while ((path = [e nextObject])) {
1406 if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1409 if (!isDir) continue;
1411 /* check whether an appropriate bundle is contained in 'path' */
1415 dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1416 while ((tmp = [dir nextObject])) {
1417 NSDictionary *bundleInfo = nil;
1418 NSArray *providedResources = nil;
1421 tmp = [path stringByAppendingPathComponent:tmp];
1422 infoPath = [self makeBundleInfoPath:tmp];
1424 //NSLog(@" info path: %@", tmp);
1426 if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1427 if (![fm fileExistsAtPath:infoPath])
1430 bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1434 [(NSDictionary *)[bundleInfo objectForKey:@"provides"]
1435 objectForKey:_resourceType];
1436 if (providedResources == nil) continue;
1438 [result addObjectsFromArray:providedResources];
1442 /* check for direct match */
1444 tmp = [self makeBundleInfoPath:path];
1446 if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1447 if ([fm fileExistsAtPath:tmp])
1448 info = [self _loadBundleInfoAtExistingPath:tmp];
1452 // direct match (a bundle was specified in the path)
1453 NSArray *providedResources;
1454 NSDictionary *provides;
1456 provides = [(NSDictionary *)info objectForKey:@"provides"];
1457 providedResources = [provides objectForKey:_resourceType];
1459 if (providedResources == nil) continue;
1461 [result addObjectsFromArray:providedResources];
1465 return [result allObjects];
1468 - (NSBundle *)bundleProvidingResourceOfType:(NSString *)_resourceType
1469 matchingQualifier:(EOQualifier *)_qual
1471 NSFileManager *fm = [NSFileManager defaultManager];
1475 /* foreach search path entry */
1477 e = [self->bundleSearchPaths objectEnumerator];
1478 while ((path = [e nextObject])) {
1481 if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1484 if (!isDir) continue;
1486 /* check whether an appropriate bundle is contained in 'path' */
1490 dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1491 while ((tmp = [dir nextObject])) {
1492 NSDictionary *bundleInfo;
1493 NSArray *providedResources;
1496 tmp = [path stringByAppendingPathComponent:tmp];
1497 infoPath = [self makeBundleInfoPath:tmp];
1499 if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1500 if (![fm fileExistsAtPath:infoPath])
1503 bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1506 bundleInfo = [bundleInfo objectForKey:@"provides"];
1507 providedResources = [bundleInfo objectForKey:_resourceType];
1509 if (providedResources == nil) continue;
1512 [providedResources filteredArrayUsingQualifier:_qual];
1514 if ([providedResources count] > 0)
1515 return [self bundleWithPath:tmp];
1519 /* check for direct match */
1521 tmp = [self makeBundleInfoPath:path];
1523 if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1524 if ([fm fileExistsAtPath:tmp])
1525 info = [self _loadBundleInfoAtExistingPath:tmp];
1529 // direct match (a bundle was specified in the path)
1530 NSArray *providedResources;
1531 NSDictionary *provides;
1533 provides = [(NSDictionary *)info objectForKey:@"provides"];
1534 providedResources = [provides objectForKey:_resourceType];
1536 if (providedResources == nil) continue;
1539 [providedResources filteredArrayUsingQualifier:_qual];
1541 if ([providedResources count] > 0)
1542 return [self bundleWithPath:path];
1549 - (NSBundle *)bundlesProvidingResourcesOfType:(NSString *)_resourceType
1550 matchingQualifier:(EOQualifier *)_qual
1552 NSMutableArray *bundles = nil;
1553 NSFileManager *fm = [NSFileManager defaultManager];
1557 bundles = [NSMutableArray arrayWithCapacity:128];
1559 /* foreach search path entry */
1561 e = [self->bundleSearchPaths objectEnumerator];
1562 while ((path = [e nextObject])) {
1565 if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
1568 if (!isDir) continue;
1570 /* check whether an appropriate bundle is contained in 'path' */
1574 dir = [[fm directoryContentsAtPath:path] objectEnumerator];
1575 while ((tmp = [dir nextObject])) {
1576 NSDictionary *bundleInfo = nil;
1577 NSArray *providedResources = nil;
1580 tmp = [path stringByAppendingPathComponent:tmp];
1581 infoPath = [self makeBundleInfoPath:tmp];
1583 if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) {
1584 if (![fm fileExistsAtPath:infoPath])
1587 bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath];
1590 bundleInfo = [bundleInfo objectForKey:@"provides"];
1591 providedResources = [bundleInfo objectForKey:_resourceType];
1593 if (providedResources == nil) continue;
1596 [providedResources filteredArrayUsingQualifier:_qual];
1598 if ([providedResources count] > 0)
1599 [bundles addObject:[self bundleWithPath:tmp]];
1603 /* check for direct match */
1605 tmp = [self makeBundleInfoPath:path];
1607 if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) {
1608 if ([fm fileExistsAtPath:tmp])
1609 info = [self _loadBundleInfoAtExistingPath:tmp];
1613 // direct match (a bundle was specified in the path)
1614 NSArray *providedResources;
1615 NSDictionary *provides;
1617 provides = [(NSDictionary *)info objectForKey:@"provides"];
1618 providedResources = [provides objectForKey:_resourceType];
1620 if (providedResources == nil) continue;
1623 [providedResources filteredArrayUsingQualifier:_qual];
1625 if ([providedResources count] > 0)
1626 [bundles addObject:[self bundleWithPath:path]];
1630 return [[bundles copy] autorelease];
1635 - (void)_bundleDidLoadNotifcation:(NSNotification *)_notification {
1636 NSDictionary *ui = [_notification userInfo];
1639 NSLog(@"bundle %@ did load with classes %@",
1640 [[_notification object] bundlePath],
1641 [ui objectForKey:@"NSLoadedClasses"]);
1644 [self registerBundle:[_notification object]
1645 classes:[ui objectForKey:@"NSLoadedClasses"]
1646 categories:[ui objectForKey:@"NSLoadedCategories"]];
1649 @end /* NGBundleManager */
1651 @implementation NSBundle(BundleManagerSupport)
1654 return [NGBundle alloc];
1656 + (id)allocWithZone:(NSZone *)zone {
1657 return [NGBundle allocWithZone:zone];
1660 #if !(NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY)
1661 //#warning remember, bundleForClass is not overridden !
1663 + (NSBundle *)bundleForClass:(Class)aClass {
1664 return [[NGBundleManager defaultBundleManager] bundleForClass:aClass];
1667 + (NSBundle *)bundleWithPath:(NSString*)path {
1668 return [[NGBundleManager defaultBundleManager] bundleWithPath:path];
1672 @end /* NSBundle(BundleManagerSupport) */
1674 @implementation NSBundle(NGBundleManagerExtensions)
1676 - (id)principalObject {
1677 return [[NGBundleManager defaultBundleManager]
1678 principalObjectOfBundle:self];
1681 - (NSArray *)providedResourcesOfType:(NSString *)_resourceType {
1682 return [[NGBundleManager defaultBundleManager]
1683 providedResourcesOfType:_resourceType
1687 - (NSString *)bundleName {
1688 return [[[self bundlePath] lastPathComponent] stringByDeletingPathExtension];
1691 - (NSString *)bundleType {
1692 return [[self bundlePath] pathExtension];
1695 - (NSArray *)providedClasses {
1696 return [[NGBundleManager defaultBundleManager] classesProvidedByBundle:self];
1699 - (NSArray *)requiredClasses {
1700 return [[NGBundleManager defaultBundleManager] classesRequiredByBundle:self];
1703 - (NSArray *)requiredBundles {
1704 return [[NGBundleManager defaultBundleManager] bundlesRequiredByBundle:self];
1707 - (NSDictionary *)configForResource:(id)_resource ofType:(NSString *)_type {
1708 return [[NGBundleManager defaultBundleManager]
1709 configForResource:_resource ofType:_type
1710 providedByBundle:self];
1715 - (BOOL)_loadForBundleManager:(NGBundleManager *)_manager {
1719 @end /* NSBundle(NGBundleManagerExtensions) */
1721 @implementation NSBundle(NGLanguageResourceExtensions)
1723 // locating resources
1725 - (NSString *)pathForResource:(NSString *)_name ofType:(NSString *)_ext
1726 inDirectory:(NSString *)_directory
1727 languages:(NSArray *)_languages
1729 NSFileManager *fm = [NSFileManager defaultManager];
1730 NSString *path = nil;
1732 id (*objAtIdx)(id,SEL,int);
1735 ? [[self bundlePath] stringByAppendingPathComponent:_directory]
1736 : [self bundlePath];
1738 if (![fm fileExistsAtPath:path])
1741 if (_ext) _name = [_name stringByAppendingPathExtension:_ext];
1743 langCount = [_languages count];
1744 objAtIdx = (langCount > 0)
1745 ? (void*)[_languages methodForSelector:@selector(objectAtIndex:)]
1748 for (i = 0; i < langCount; i++) {
1753 ? objAtIdx(_languages, @selector(objectAtIndex:), i)
1754 : [_languages objectAtIndex:i];
1756 language = [language stringByAppendingPathExtension:@"lproj"];
1757 lpath = [path stringByAppendingPathComponent:language];
1758 lpath = [lpath stringByAppendingPathComponent:_name];
1760 if ([fm fileExistsAtPath:lpath])
1764 /* now look into x.bundle/Resources/name.type */
1765 if ([fm fileExistsAtPath:[path stringByAppendingPathComponent:_name]])
1766 return [path stringByAppendingPathComponent:_name];
1771 - (NSString *)pathForResource:(NSString *)_name ofType:(NSString *)_ext
1772 languages:(NSArray *)_languages
1776 path = [self pathForResource:_name ofType:_ext
1777 inDirectory:@"Resources"
1778 languages:_languages];
1779 if (path) return path;
1781 path = [self pathForResource:_name ofType:_ext
1783 languages:_languages];
1787 @end /* NSBundle(NGLanguageResourceExtensions) */
1789 @implementation NGBundle
1792 return [self allocWithZone:NULL];
1794 + (id)allocWithZone:(NSZone*)zone {
1795 return NSAllocateObject(self, 0, zone);
1798 - (id)initWithPath:(NSString *)__path {
1799 return [super initWithPath:__path];
1804 - (BOOL)_loadForBundleManager:(NGBundleManager *)_manager {
1805 return [super load];
1809 NGBundleManager *bm;
1811 bm = [NGBundleManager defaultBundleManager];
1813 return [bm loadBundle:self] ? YES : NO;
1816 + (NSBundle *)bundleForClass:(Class)aClass {
1817 return [[NGBundleManager defaultBundleManager] bundleForClass:aClass];
1819 + (NSBundle *)bundleWithPath:(NSString*)path {
1820 return [[NGBundleManager defaultBundleManager] bundleWithPath:path];
1823 #if GNUSTEP_BASE_LIBRARY
1825 - (Class)principalClass {
1829 if ((c = [super principalClass]))
1832 if ((cname = [[self infoDictionary] objectForKey:@"NSPrincipalClass"]) ==nil)
1835 if ((c = NSClassFromString(cname)))
1838 NSLog(@"%s: did not find principal class named '%@' of bundle %@",
1839 __PRETTY_FUNCTION__, cname, self);
1845 - (NSString *)description {
1849 "<%s %p fullPath: %s infoDictionary: %p loaded=%s>",
1850 (char*)object_get_class_name(self),
1852 [[self bundlePath] cString],
1853 [self infoDictionary],
1854 self->_codeLoaded ? "yes" : "no");
1856 return [NSString stringWithCString:buffer];