/* Copyright (C) 2000-2004 SKYRIX Software AG This file is part of OpenGroupware.org. OGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. OGo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // $Id: NGBundleManager.m 4 2004-08-20 17:04:31Z helge $ #include "NGBundleManager.h" #include "common.h" #import #import #include #if 0 && GNUSTEP_BASE_LIBRARY /* supported in repository since 19990916-2254-MET */ /* hack until GNUstep provides the necessary callbacks */ # define NSNonRetainedObjectMapValueCallBacks NSObjectMapValueCallBacks #endif #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY # include #endif #if NeXT_RUNTIME || APPLE_RUNTIME #include //OBJC_EXPORT void objc_setClassHandler(int (*)(const char *)); static BOOL debugClassHook = NO; static int _getClassHook(const char *className) { if (className == NULL) return 0; if (debugClassHook) printf("lookup class '%s'.\n", className); if (objc_lookUpClass(className)) return 1; { static NGBundleManager *manager = nil; NSBundle *bundle; if (debugClassHook) NSLog(@"%s: look for class %s", __PRETTY_FUNCTION__, className); if (manager == nil) manager = [NGBundleManager defaultBundleManager]; bundle = [manager bundleForClassNamed: [NSString stringWithCString:className]]; if (bundle) { if (debugClassHook) { NSLog(@"%s: found bundle %@", __PRETTY_FUNCTION__, [bundle bundlePath]); } if (![manager loadBundle:bundle]) { fprintf(stderr, "bundleManager couldn't load bundle for class '%s'.\n", className); } #if 0 else { Class c = objc_lookUpClass(className); NSLog(@"%s: loaded bundle %@ for className %s class %@", __PRETTY_FUNCTION__, bundle, className, c); } #endif } } return 1; } #endif #if GNU_RUNTIME #include static Class (*oldClassLoadHook)(const char *_name) = NULL; static inline BOOL _isValidClassName(const char *_name) { register int len; if (_name == NULL) return NO; for (len = 0; (len < 256) && (*_name != '\0'); len++, _name++) { if (*_name != '_') { if (!isalnum((int)*_name)) return NO; } } return (len == 256) ? NO : YES; } static Class _classLoadHook(const char *_name) { if (_isValidClassName(_name)) { static NGBundleManager *manager = nil; NSBundle *bundle; //NSLog(@"%s: look for class %s", __PRETTY_FUNCTION__, _name); if (manager == nil) manager = [NGBundleManager defaultBundleManager]; bundle = [manager bundleForClassNamed:[NSString stringWithCString:_name]]; if (bundle) { //NSLog(@"%s: found bundle %@", __PRETTY_FUNCTION__, [bundle bundlePath]); if ([manager loadBundle:bundle]) { Class clazz; void *hook; hook = _objc_lookup_class; _objc_lookup_class = NULL; clazz = objc_lookup_class(_name); _objc_lookup_class = hook; if (clazz) return clazz; } } } return (oldClassLoadHook != NULL) ? oldClassLoadHook(_name) : Nil; } #endif // GNU_RUNTIME NSString *NGBundleWasLoadedNotificationName = @"NGBundleWasLoadedNotification"; @interface NSBundle(NGBundleManagerPrivate) - (BOOL)_loadForBundleManager:(NGBundleManager *)_manager; @end @interface NGBundleManager(PrivateMethods) - (void)registerBundle:(NSBundle *)_bundle classes:(NSArray *)_classes categories:(NSArray *)_categories; - (NSString *)pathForBundleProvidingResource:(NSString *)_resourceName ofType:(NSString *)_type resourceSelector:(NGBundleResourceSelector)_selector context:(void *)_ctx; - (NSString *)makeBundleInfoPath:(NSString *)_path; @end static BOOL _selectClassByVersion(NSString *_resourceName, NSString *_resourceType, NSString *_path, NSDictionary *_resourceConfig, NGBundleManager *_bundleManager, void *_version) { id tmp; int classVersion; if (![_resourceType isEqualToString:@"classes"]) return NO; if (_version == NULL) return YES; if ([(id)_version intValue] == -1) return YES; if ((tmp = [_resourceConfig objectForKey:@"version"])) { classVersion = [tmp intValue]; if (classVersion < [(id)_version intValue]) { NSLog(@"WARNING: class version mismatch for class %@: " @"requested at least version %i, got version %i", _resourceName, [(id)_version intValue], classVersion); } } if ((tmp = [_resourceConfig objectForKey:@"exact-version"])) { classVersion = [tmp intValue]; if (classVersion != [(id)_version intValue]) { NSLog(@"WARNING: class version mismatch for class %@: " @"requested exact version %i, got version %i", _resourceName, [(id)_version intValue], classVersion); } } return YES; } @implementation NGBundleManager // THREAD static NGBundleManager *defaultManager = nil; static BOOL debugOn = NO; + (void)initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; debugOn = [ud boolForKey:@"NGBundleManagerDebugEnabled"]; } #if GNU_RUNTIME && 0 + (void)load { if (_objc_lookup_class != _classLoadHook) { oldClassLoadHook = _objc_lookup_class; _objc_lookup_class = _classLoadHook; } } #endif + (id)defaultBundleManager { if (defaultManager == nil) { defaultManager = [[NGBundleManager alloc] init]; #if NeXT_RUNTIME || APPLE_RUNTIME { static BOOL didRegisterCallback = NO; if (!didRegisterCallback) { didRegisterCallback = YES; objc_setClassHandler(_getClassHook); } } #endif #if GNU_RUNTIME if (_objc_lookup_class != _classLoadHook) { oldClassLoadHook = _objc_lookup_class; _objc_lookup_class = _classLoadHook; } #endif } return defaultManager; } - (void)_setupBundleSearchPathes { NSProcessInfo *pi; NSUserDefaults *ud; id paths; NSString *path; pi = [NSProcessInfo processInfo]; /* setup bundle search path */ self->bundleSearchPaths = [[NSMutableArray alloc] init]; // first add main-bundle path path = [[[pi arguments] objectAtIndex:0] stringByDeletingLastPathComponent]; if ([path isEqual:@""]) path = @"."; #if WITH_GNUSTEP else { /* The path is the complete path to the executable, including the processor, the OS and the library combo. Strip these directories from the main bundle's path. */ path = [[[path stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent]; } #endif [self->bundleSearchPaths addObject:path]; // look into NGBundlePath default if ((ud = [NSUserDefaults standardUserDefaults]) == nil) { // got this with gstep-base during the port, apparently it happens // if the bundle manager is created inside the setup process of // gstep-base (for whatever reason) NSLog(@"ERROR(NGBundleManager): got no system userdefaults object!"); #if DEBUG abort(); #endif } paths = [ud arrayForKey:@"NGBundlePath"]; if (paths == nil) { paths = [ud stringForKey:@"NGBundlePath"]; if (paths) { #if defined(__MINGW32__) paths = [paths componentsSeparatedByString:@";"]; #else paths = [paths componentsSeparatedByString:@":"]; #endif } } if (paths) [self->bundleSearchPaths addObjectsFromArray:paths]; else if (debugOn) NSLog(@"Note: NGBundlePath default is not configured."); /* look into environment */ paths = [[pi environment] objectForKey:@"NGBundlePath"]; if (paths) { #if defined(__MINGW32__) paths = [paths componentsSeparatedByString:@";"]; #else paths = [paths componentsSeparatedByString:@":"]; #endif } if (paths) [self->bundleSearchPaths addObjectsFromArray:paths]; /* add standard bundle paths */ { #if !GNUSTEP #else // TODO: whats that supposed to do? // TODO: replace with proper path locator function! NSDictionary *env; NSString *p; unsigned i, count; id tmp; env = [[NSProcessInfo processInfo] environment]; if ((tmp = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil) tmp = [env objectForKey:@"GNUSTEP_PATHLIST"]; tmp = [tmp componentsSeparatedByString:@":"]; for (i = 0, count = [tmp count]; i < count; i++) { p = [tmp objectAtIndex:i]; p = [p stringByAppendingPathComponent:@"Library"]; p = [p stringByAppendingPathComponent:@"Bundles"]; if ([self->bundleSearchPaths containsObject:p]) continue; if (p) [self->bundleSearchPaths addObject:p]; } #endif } #if DEBUG && NeXT_Foundation_LIBRARY && 0 NSLog(@"%s: bundle search pathes:\n%@", __PRETTY_FUNCTION__, self->bundleSearchPaths); #endif } - (void)_registerLoadedBundles { NSEnumerator *currentBundles; NSBundle *loadedBundle; currentBundles = [[NSBundle allBundles] objectEnumerator]; while ((loadedBundle = [currentBundles nextObject])) [self registerBundle:loadedBundle classes:nil categories:nil]; } - (void)_registerForBundleLoadNotification { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_bundleDidLoadNotifcation:) name:@"NSBundleDidLoadNotification" object:nil]; } - (id)init { #if GNUSTEP_BASE_LIBRARY if ([NSUserDefaults standardUserDefaults] == nil) { /* called inside setup process, deny creation (HACK) */ [self release]; return nil; } #endif if ((self = [super init])) { self->classToBundle = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, NSNonRetainedObjectMapValueCallBacks, 32); self->classNameToBundle = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonRetainedObjectMapValueCallBacks, 32); self->categoryNameToBundle = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonRetainedObjectMapValueCallBacks, 32); self->pathToBundle = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonRetainedObjectMapValueCallBacks, 32); self->pathToBundleInfo = NSCreateMapTable(NSObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 32); self->nameToBundle = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonRetainedObjectMapValueCallBacks, 32); self->loadedBundles = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 32); [self _setupBundleSearchPathes]; [self _registerLoadedBundles]; [self _registerForBundleLoadNotification]; } return self; } - (void)dealloc { [self->loadingBundles release]; if (self->loadedBundles) NSFreeMapTable(self->loadedBundles); if (self->classToBundle) NSFreeMapTable(self->classToBundle); if (self->classNameToBundle) NSFreeMapTable(self->classNameToBundle); if (self->categoryNameToBundle) NSFreeMapTable(self->categoryNameToBundle); if (self->pathToBundle) NSFreeMapTable(self->pathToBundle); if (self->pathToBundleInfo) NSFreeMapTable(self->pathToBundleInfo); if (self->nameToBundle) NSFreeMapTable(self->nameToBundle); [self->bundleSearchPaths release]; [super dealloc]; } /* registering bundles */ - (void)registerBundle:(NSBundle *)_bundle classes:(NSArray *)_classes categories:(NSArray *)_categories { NSEnumerator *e; id v; //NSLog(@"NGBundleManager: register loaded bundle %@", [_bundle bundlePath]); e = [_classes objectEnumerator]; while ((v = [e nextObject])) { NSMapInsert(self->classToBundle, NSClassFromString(v), _bundle); NSMapInsert(self->classNameToBundle, v, _bundle); } e = [_categories objectEnumerator]; while ((v = [e nextObject])) NSMapInsert(self->categoryNameToBundle, v, _bundle); } /* bundle locator */ - (NSString *)pathForBundleWithName:(NSString *)_name type:(NSString *)_type { NSFileManager *fm = [NSFileManager defaultManager]; NSEnumerator *e; NSString *path; NSString *bundlePath; NSBundle *bundle; /* first check in table */ bundlePath = [_name stringByAppendingPathExtension:_type]; if ((bundle = NSMapGet(self->nameToBundle, bundlePath))) return [bundle bundlePath]; e = [self->bundleSearchPaths objectEnumerator]; while ((path = [e nextObject])) { BOOL isDir = NO; if ([fm fileExistsAtPath:path isDirectory:&isDir]) { if (!isDir) continue; if ([[path lastPathComponent] isEqualToString:bundlePath]) { // direct match (a bundle was specified in the path) return path; } else { NSString *tmp; tmp = [path stringByAppendingPathComponent:bundlePath]; if ([fm fileExistsAtPath:tmp isDirectory:&isDir]) { if (isDir) // found bundle return tmp; } } } } return nil; } /* getting bundles */ - (NSBundle *)bundleForClass:(Class)aClass { /* this method never loads a dynamic bundle (since the class is set up) */ NSBundle *bundle; bundle = NSMapGet(self->classToBundle, aClass); #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY if (bundle == nil){ bundle = [NSBundle bundleForClass:aClass]; if (bundle == [NSBundle mainBundle]) bundle = nil; } #endif if (bundle == nil) { /* if the class wasn't loaded from a bundle, it's *either* the main bundle or a bundle loaded before NGExtension was loaded !!! */ #if !LIB_FOUNDATION_LIBRARY && !GNUSTEP_BASE_LIBRARY # warning incorrect behaviour if NGExtensions is dynamically loaded ! // TODO: can we do anything about this? Can we detect the situation and // print a log instead of the compile warning? #endif bundle = [NSBundle mainBundle]; NSMapInsert(self->classToBundle, aClass, bundle); NSMapInsert(self->classNameToBundle, NSStringFromClass(aClass), bundle); } return bundle; } - (NSBundle *)bundleWithPath:(NSString *)path { NSBundle *bundle = nil; NSString *bn; path = [path stringByResolvingSymlinksInPath]; if (path == nil) return nil; if (debugOn) NSLog(@"find bundle for path: '%@'", path); bundle = NSMapGet(self->pathToBundle, path); if (bundle) { if (debugOn) NSLog(@" found: %@", bundle); return bundle; } if ((bundle = [(NGBundle *)[NGBundle alloc] initWithPath:path]) == nil) { NSLog(@"ERROR(%s): could not create bundle for path: '%@'", __PRETTY_FUNCTION__, path); return nil; } bn = [[bundle bundleName] stringByAppendingPathExtension:[bundle bundleType]], NSMapInsert(self->pathToBundle, path, bundle); NSMapInsert(self->nameToBundle, bn, bundle); return bundle; } - (NSBundle *)bundleWithName:(NSString *)_name type:(NSString *)_type { NSBundle *bundle; NSString *bn; bn = [_name stringByAppendingPathExtension:_type]; bundle = NSMapGet(self->nameToBundle, bn); if (bundle == nil) bundle = [self bundleWithPath:[self pathForBundleWithName:_name type:_type]]; if (![[bundle bundleType] isEqualToString:_type]) bundle = nil; return bundle; } - (NSBundle *)bundleWithName:(NSString *)_name { return [self bundleWithName:_name type:@"bundle"]; } - (NSBundle *)bundleForClassNamed:(NSString *)_className { NSString *path = nil; NSBundle *bundle = nil; if (_className == nil) return nil; /* first check in table */ if ((bundle = NSMapGet(self->classNameToBundle, _className))) return bundle; #if GNU_RUNTIME /* then look in runtime, reset load callback to avoid recursion */ { // THREAD void *loadCallback; Class clazz; loadCallback = _objc_lookup_class; _objc_lookup_class = NULL; clazz = NSClassFromString(_className); _objc_lookup_class = loadCallback; if (clazz) { /* the class is already loaded */ bundle = [self bundleForClass:clazz]; NSMapInsert(self->classNameToBundle, _className, bundle); return bundle; } } #endif path = [self pathForBundleProvidingResource:_className ofType:@"classes" resourceSelector:_selectClassByVersion context:NULL /* version */]; if (path) { path = [path stringByResolvingSymlinksInPath]; NSAssert(path, @"couldn't resolve symlinks in path .."); } if (path == nil) return nil; if ((bundle = [self bundleWithPath:path])) NSMapInsert(self->classNameToBundle, _className, bundle); return bundle; } // dependencies + (int)version { return 2; } - (NSArray *)bundlesRequiredByBundle:(NSBundle *)_bundle { [self doesNotRecognizeSelector:_cmd]; return nil; } - (NSArray *)classesProvidedByBundle:(NSBundle *)_bundle { [self doesNotRecognizeSelector:_cmd]; return nil; } - (NSArray *)classesRequiredByBundle:(NSBundle *)_bundle { [self doesNotRecognizeSelector:_cmd]; return nil; } // initialization - (NSString *)makeBundleInfoPath:(NSString *)_path { #if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) && !defined(GSWARN) return [[[_path stringByAppendingPathComponent:@"Contents"] stringByAppendingPathComponent:@"Resources"] stringByAppendingPathComponent:@"bundle-info.plist"]; #else return [_path stringByAppendingPathComponent:@"bundle-info.plist"]; #endif } - (id)_initializeLoadedBundle:(NSBundle *)_bundle info:(NSDictionary *)_bundleInfo { id handler = nil; /* check whether a handler was specified */ if ((handler = [_bundleInfo objectForKey:@"bundleHandler"])) { if ((handler = NSClassFromString(handler)) == nil) { NSLog(@"ERROR: did not find handler class %@ of bundle %@.", [_bundleInfo objectForKey:@"bundleHandler"], [_bundle bundlePath]); handler = [_bundle principalClass]; } handler = [handler alloc]; if ([handler respondsToSelector:@selector(initForBundle:bundleManager:)]) handler = [handler initForBundle:_bundle bundleManager:self]; else handler = [handler init]; handler = [handler autorelease]; if (handler == nil) { NSLog(@"ERROR: could not instantiate handler class %@ of bundle %@.", [_bundleInfo objectForKey:@"bundleHandler"], [_bundle bundlePath]); handler = [_bundle principalClass]; } } else { if ((handler = [_bundle principalClass]) == nil) /* use NGBundle class as default bundle handler */ handler = [NGBundle class]; } return handler; } /* loading */ - (NSDictionary *)_loadBundleInfoAtExistingPath:(NSString *)_path { NSDictionary *bundleInfo; id info; #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY bundleInfo = NGParsePropertyListFromFile(_path); #else bundleInfo = [NSDictionary dictionaryWithContentsOfFile:_path]; #endif if (bundleInfo == nil) { NSLog(@"couldn't load bundle-info at path '%@' !", _path); return nil; } /* check required bundle manager version */ info = [bundleInfo objectForKey:@"requires"]; if ((info = [(NSDictionary *)info objectForKey:@"bundleManagerVersion"])) { if ([info intValue] > [[self class] version]) { /* bundle manager version does not match ... */ return nil; } } NSMapInsert(self->pathToBundleInfo, _path, bundleInfo); return bundleInfo; } - (NSBundle *)_locateBundleForClassInfo:(NSDictionary *)_classInfo { NSString *className; NSBundle *bundle; if (_classInfo == nil) return nil; if ((className = [_classInfo objectForKey:@"name"]) == nil) { NSLog(@"ERROR: missing classname in bundle-info.plist class section !"); return nil; } if ((bundle = [self bundleForClassNamed:className]) == nil) { #if 0 // class might be already loaded NSLog(@"ERROR: did not find class %@ required by bundle %@.", className, [_bundle bundlePath]); #endif } return bundle; } - (NSArray *)_locateBundlesForClassInfos:(NSEnumerator *)_classInfos { NSMutableArray *requiredBundles; NSDictionary *i; requiredBundles = [NSMutableArray arrayWithCapacity:16]; while ((i = [_classInfos nextObject])) { NSBundle *bundle; if ((bundle = [self _locateBundleForClassInfo:i]) == nil) continue; [requiredBundles addObject:bundle]; } return requiredBundles; } - (BOOL)_preLoadBundle:(NSBundle *)_bundle info:(NSDictionary *)_bundleInfo { /* TODO: split up this huge method */ NSDictionary *requires = nil; NSMutableArray *requiredBundles = nil; NSBundle *requiredBundle = nil; requires = [_bundleInfo objectForKey:@"requires"]; if (requires == nil) /* invalid bundle info specified */ return YES; /* load required bundles */ { NSEnumerator *e; NSDictionary *i; /* locate required bundles */ e = [[requires objectForKey:@"bundles"] objectEnumerator]; while ((i = [e nextObject])) { NSString *bundleName; if (![i respondsToSelector:@selector(objectForKey:)]) { NSLog(@"ERROR(%s): invalid bundle-info of bundle %@ !!!\n" @" requires-entry is not a dictionary: %@", __PRETTY_FUNCTION__, _bundle, i); continue; } if ((bundleName = [i objectForKey:@"name"])) { NSString *type; type = [i objectForKey:@"type"]; if (type == nil) type = @"bundle"; if ((requiredBundle = [self bundleWithName:bundleName type:type])) { if (requiredBundles == nil) requiredBundles = [NSMutableArray arrayWithCapacity:16]; [requiredBundles addObject:requiredBundle]; } else { NSLog(@"ERROR(NGBundleManager): did not find bundle '%@' (type=%@) " @"required by bundle %@.", bundleName, type, [_bundle bundlePath]); continue; } } else NSLog(@"ERROR: error in bundle-info.plist of bundle %@", _bundle); } } /* load located bundles */ { NSEnumerator *e; e = [requiredBundles objectEnumerator]; while ((requiredBundle = [e nextObject])) { Class bundleMaster; if ((bundleMaster = [self loadBundle:requiredBundle]) == Nil) { NSLog(@"ERROR: could not load bundle %@ (%@) required by bundle %@.", [requiredBundle bundlePath], requiredBundle, [_bundle bundlePath]); continue; } } } /* load required classes */ { NSArray *bundles; NSArray *reqClasses; reqClasses = [requires objectForKey:@"classes"]; bundles = [self _locateBundlesForClassInfos:[reqClasses objectEnumerator]]; if (requiredBundles == nil) requiredBundles = [NSMutableArray arrayWithCapacity:16]; [requiredBundles addObjectsFromArray:bundles]; } /* load located bundles */ { NSEnumerator *e; e = [requiredBundles objectEnumerator]; while ((requiredBundle = [e nextObject])) { Class bundleMaster; if ((bundleMaster = [self loadBundle:requiredBundle]) == Nil) { NSLog(@"ERROR: could not load bundle %@ (%@) required by bundle %@.", [requiredBundle bundlePath], requiredBundle, [_bundle bundlePath]); continue; } } } /* check whether versions of classes match */ { NSEnumerator *e; NSDictionary *i; e = [[requires objectForKey:@"classes"] objectEnumerator]; while ((i = [e nextObject])) { NSString *className; Class clazz; if ((className = [i objectForKey:@"name"]) == nil) continue; if ((clazz = NSClassFromString(className)) == Nil) continue; if ([i objectForKey:@"exact-version"]) { int v; v = [[i objectForKey:@"exact-version"] intValue]; if (v != [clazz version]) { NSLog(@"ERROR: required exact class match failed:\n" @" class: %@\n" @" required version: %i\n" @" loaded version: %i\n" @" bundle: %@", className, v, [clazz version], [_bundle bundlePath]); } } else if ([i objectForKey:@"version"]) { int v; v = [[i objectForKey:@"version"] intValue]; if (v > [clazz version]) { NSLog(@"ERROR: provided class does not match required version:\n" @" class: %@\n" @" least required version: %i\n" @" loaded version: %i\n" @" bundle: %@", className, v, [clazz version], [_bundle bundlePath]); } } } } return YES; } - (BOOL)_postLoadBundle:(NSBundle *)_bundle info:(NSDictionary *)_bundleInfo { return YES; } - (id)loadBundle:(NSBundle *)_bundle { NSString *path = nil; NSDictionary *bundleInfo = nil; id bundleManager = nil; #if DEBUG NSAssert(self->loadedBundles, @"missing loadedBundles hashmap .."); #endif if ((bundleManager = NSMapGet(self->loadedBundles, _bundle))) return bundleManager; if (_bundle == [NSBundle mainBundle]) return [NSBundle mainBundle]; if ([self->loadingBundles containsObject:_bundle]) // recursive call return nil; if (self->loadingBundles == nil) self->loadingBundles = [[NSMutableSet allocWithZone:[self zone]] init]; [self->loadingBundles addObject:_bundle]; path = [_bundle bundlePath]; path = [self makeBundleInfoPath:path]; if ((bundleInfo = NSMapGet(self->pathToBundleInfo, path)) == nil) { if ([[NSFileManager defaultManager] fileExistsAtPath:path]) bundleInfo = [self _loadBundleInfoAtExistingPath:path]; } if (![self _preLoadBundle:_bundle info:bundleInfo]) goto done; if (![_bundle _loadForBundleManager:self]) goto done; if (![self _postLoadBundle:_bundle info:bundleInfo]) goto done; if ((bundleManager = [self _initializeLoadedBundle:_bundle info:bundleInfo])) { NSMapInsert(self->loadedBundles, _bundle, bundleManager); if ([bundleManager respondsToSelector: @selector(bundleManager:didLoadBundle:)]) [bundleManager bundleManager:self didLoadBundle:_bundle]; } #if 0 else { NSLog(@"ERROR(%s): couldn't initialize loaded bundle '%@'", __PRETTY_FUNCTION__, [_bundle bundlePath]); } #endif done: [self->loadingBundles removeObject:_bundle]; if (bundleManager) { if (bundleInfo == nil) bundleInfo = [NSDictionary dictionary]; [[NSNotificationCenter defaultCenter] postNotificationName: NGBundleWasLoadedNotificationName object:_bundle userInfo:[NSDictionary dictionaryWithObjectsAndKeys: self, @"NGBundleManager", bundleManager, @"NGBundleHandler", bundleInfo, @"NGBundleInfo", nil]]; } return bundleManager; } // manager - (id)principalObjectOfBundle:(NSBundle *)_bundle { return (id)NSMapGet(self->loadedBundles, _bundle); } // resources static BOOL _doesInfoMatch(NSArray *keys, NSDictionary *dict, NSDictionary *info) { int i, count; for (i = 0, count = [keys count]; i < count; i++) { NSString *key; id kv, vv; key = [keys objectAtIndex:i]; vv = [info objectForKey:key]; if (vv == nil) { /* info has no matching key */ return NO; } kv = [dict objectForKey:key]; if (![kv isEqual:vv]) return NO; } return YES; } - (NSDictionary *)configForResource:(id)_resource ofType:(NSString *)_type providedByBundle:(NSBundle *)_bundle { NSDictionary *bundleInfo = nil; NSString *infoPath; NSEnumerator *providedResources; NSArray *rnKeys = nil; int rnKeyCount = 0; id info; if ([_resource respondsToSelector:@selector(objectForKey:)]) { rnKeys = [_resource allKeys]; rnKeyCount = [rnKeys count]; } infoPath = [self makeBundleInfoPath:[_bundle bundlePath]]; /* check whether info is in cache */ if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) { if (![[NSFileManager defaultManager] fileExistsAtPath:infoPath]) /* no bundle-info.plist available .. */ return nil; /* load info */ bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath]; } /* get provided resources config */ providedResources = [[(NSDictionary *)[bundleInfo objectForKey:@"provides"] objectForKey:_type] objectEnumerator]; if (providedResources == nil) return nil; /* scan provided resources */ while ((info = [providedResources nextObject])) { if (rnKeys) { if (!_doesInfoMatch(rnKeys, _resource, info)) continue; } else { NSString *name; name = [[(NSDictionary *)info objectForKey:@"name"] stringValue]; if (name == nil) continue; if (![name isEqualToString:_resource]) continue; } return info; } return nil; } - (void)_processInfoForProvidedResources:(NSDictionary *)info ofType:(NSString *)_type path:(NSString *)path resourceName:(NSString *)_resourceName resourceSelector:(NGBundleResourceSelector)_selector context:(void *)_context andAddToResultArray:(NSMutableArray *)result { NSEnumerator *providedResources = nil; if (info == nil) return; /* direct match (a bundle was specified in the path) */ providedResources = [[(NSDictionary *)[info objectForKey:@"provides"] objectForKey:_type] objectEnumerator]; info = nil; if (providedResources == nil) return; /* scan provide array */ while ((info = [providedResources nextObject])) { NSString *name; if ((name = [[info objectForKey:@"name"] stringValue]) == nil) continue; if (_resourceName) { if (![name isEqualToString:_resourceName]) continue; } if (_selector) { if (!_selector(_resourceName, _type, path, info, self, _context)) continue; } [result addObject:path]; } } - (NSArray *)pathsForBundlesProvidingResource:(NSString *)_resourceName ofType:(NSString *)_type resourceSelector:(NGBundleResourceSelector)_selector context:(void *)_context { /* TODO: split up method */ NSMutableArray *result = nil; NSFileManager *fm; NSEnumerator *e; NSString *path; fm = [NSFileManager defaultManager]; result = [NSMutableArray arrayWithCapacity:64]; e = [self->bundleSearchPaths objectEnumerator]; while ((path = [e nextObject])) { BOOL isDir = NO; if ([fm fileExistsAtPath:path isDirectory:&isDir]) { NSString *tmp; id info = nil; if (!isDir) continue; /* check whether an appropriate bundle is contained in 'path' */ { NSEnumerator *dir; dir = [[fm directoryContentsAtPath:path] objectEnumerator]; while ((tmp = [dir nextObject])) { NSDictionary *bundleInfo = nil; NSEnumerator *providedResources = nil; NSString *infoPath; id info; tmp = [path stringByAppendingPathComponent:tmp]; infoPath = [self makeBundleInfoPath:tmp]; if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) { if (![fm fileExistsAtPath:infoPath]) continue; bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath]; } providedResources = [[(NSDictionary *)[bundleInfo objectForKey:@"provides"] objectForKey:_type] objectEnumerator]; if (providedResources == nil) continue; // scan provide array while ((info = [providedResources nextObject])) { NSString *name; name = [[(NSDictionary *)info objectForKey:@"name"] stringValue]; if (name == nil) continue; if (_resourceName) { if (![name isEqualToString:_resourceName]) continue; } if (_selector) { if (!_selector(name, _type, tmp, info, self, _context)) continue; } [result addObject:tmp]; break; } } } /* check for direct match */ tmp = [self makeBundleInfoPath:path]; if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) { if ([fm fileExistsAtPath:tmp]) info = [self _loadBundleInfoAtExistingPath:tmp]; } [self _processInfoForProvidedResources:info ofType:_type path:path resourceName:_resourceName resourceSelector:_selector context:_context andAddToResultArray:result]; } } return [[result copy] autorelease]; } - (NSString *)pathForBundleProvidingResource:(id)_resourceName ofType:(NSString *)_type resourceSelector:(NGBundleResourceSelector)_selector context:(void *)_context { NSFileManager *fm = [NSFileManager defaultManager]; NSEnumerator *e; NSString *path; NSArray *rnKeys = nil; int rnKeyCount = 0; if ([_resourceName respondsToSelector:@selector(objectForKey:)]) { rnKeys = [_resourceName allKeys]; rnKeyCount = [rnKeys count]; } e = [self->bundleSearchPaths objectEnumerator]; while ((path = [e nextObject])) { BOOL isDir = NO; if ([fm fileExistsAtPath:path isDirectory:&isDir]) { NSString *tmp; id info = nil; if (!isDir) continue; /* check whether an appropriate bundle is contained in 'path' */ { NSEnumerator *dir; dir = [[fm directoryContentsAtPath:path] objectEnumerator]; while ((tmp = [dir nextObject])) { NSDictionary *bundleInfo = nil; NSEnumerator *providedResources = nil; NSString *infoPath; id info; tmp = [path stringByAppendingPathComponent:tmp]; infoPath = [self makeBundleInfoPath:tmp]; if (debugOn) NSLog(@"check path path=%@ info=%@", tmp, infoPath); if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) { if (![fm fileExistsAtPath:infoPath]) continue; bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath]; } if (debugOn) { NSLog(@"found info for path=%@ info=%@: %@", tmp, infoPath, bundleInfo); } providedResources = [[(NSDictionary *)[bundleInfo objectForKey:@"provides"] objectForKey:_type] objectEnumerator]; if (providedResources == nil) continue; // scan provide array while ((info = [providedResources nextObject])) { if (rnKeys) { if (!_doesInfoMatch(rnKeys, _resourceName, info)) continue; } else { NSString *name; name = [[(NSDictionary *)info objectForKey:@"name"] stringValue]; if (name == nil) continue; if (![name isEqualToString:_resourceName]) continue; } if (_selector) { if (!_selector(_resourceName, _type, tmp, info, self, _context)) continue; } /* all conditions applied */ return tmp; } } } /* check for direct match */ tmp = [self makeBundleInfoPath:path]; if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) { if ([fm fileExistsAtPath:tmp]) info = [self _loadBundleInfoAtExistingPath:tmp]; else if (debugOn) { NSLog(@"WARNING(%s): did not find direct path '%@'", __PRETTY_FUNCTION__, tmp); } } if (info) { // direct match (a bundle was specified in the path) NSEnumerator *providedResources; NSDictionary *provides; provides = [(NSDictionary *)info objectForKey:@"provides"]; providedResources = [[provides objectForKey:_type] objectEnumerator]; info = nil; if (providedResources == nil) continue; // scan provide array while ((info = [providedResources nextObject])) { if (rnKeys) { if (!_doesInfoMatch(rnKeys, _resourceName, info)) continue; } else { NSString *name; name = [[(NSDictionary *)info objectForKey:@"name"] stringValue]; if (name == nil) continue; if (![name isEqualToString:_resourceName]) continue; } if (_selector) { if (!_selector(_resourceName, _type, tmp, info, self, _context)) continue; } /* all conditions applied */ return tmp; } } } } return nil; } - (NSBundle *)bundleProvidingResource:(id)_resourceName ofType:(NSString *)_resourceType { NSString *bp; bp = [self pathForBundleProvidingResource:_resourceName ofType:_resourceType resourceSelector:NULL context:nil]; if ([bp length] == 0) { #if (NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) && HEAVY_DEBUG NSLog(@"%s: found no resource '%@' of type '%@' ...", __PRETTY_FUNCTION__, _resourceName, _resourceType); #endif return nil; } return [self bundleWithPath:bp]; } - (NSArray *)bundlesProvidingResource:(id)_resourceName ofType:(NSString *)_type { NSArray *paths; NSMutableArray *bundles; int i, count; paths = [self pathsForBundlesProvidingResource:_resourceName ofType:_type resourceSelector:NULL context:nil]; count = [paths count]; if (paths == nil) return nil; if (count == 0) return paths; bundles = [NSMutableArray arrayWithCapacity:count]; for (i = 0; i < count; i++) { NSBundle *bundle; if ((bundle = [self bundleWithPath:[paths objectAtIndex:i]])) [bundles addObject:bundle]; } return [[bundles copy] autorelease]; } - (NSArray *)providedResourcesOfType:(NSString *)_resourceType inBundle:(NSBundle *)_bundle { NSString *path; NSDictionary *bundleInfo; path = [self makeBundleInfoPath:[_bundle bundlePath]]; if (path == nil) return nil; /* retrieve bundle info dictionary */ if ((bundleInfo = NSMapGet(self->pathToBundleInfo, path)) == nil) bundleInfo = [self _loadBundleInfoAtExistingPath:path]; return [(NSDictionary *)[bundleInfo objectForKey:@"provides"] objectForKey:_resourceType]; } - (NSArray *)providedResourcesOfType:(NSString *)_resourceType { NSMutableSet *result = nil; NSFileManager *fm = [NSFileManager defaultManager]; NSEnumerator *e; NSString *path; result = [NSMutableSet setWithCapacity:128]; e = [self->bundleSearchPaths objectEnumerator]; while ((path = [e nextObject])) { BOOL isDir = NO; if ([fm fileExistsAtPath:path isDirectory:&isDir]) { NSString *tmp; id info = nil; if (!isDir) continue; /* check whether an appropriate bundle is contained in 'path' */ { NSEnumerator *dir; dir = [[fm directoryContentsAtPath:path] objectEnumerator]; while ((tmp = [dir nextObject])) { NSDictionary *bundleInfo = nil; NSArray *providedResources = nil; NSString *infoPath; tmp = [path stringByAppendingPathComponent:tmp]; infoPath = [self makeBundleInfoPath:tmp]; //NSLog(@" info path: %@", tmp); if ((bundleInfo = NSMapGet(self->pathToBundleInfo, infoPath)) == nil) { if (![fm fileExistsAtPath:infoPath]) continue; bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath]; } providedResources = [(NSDictionary *)[bundleInfo objectForKey:@"provides"] objectForKey:_resourceType]; if (providedResources == nil) continue; [result addObjectsFromArray:providedResources]; } } /* check for direct match */ tmp = [self makeBundleInfoPath:path]; if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) { if ([fm fileExistsAtPath:tmp]) info = [self _loadBundleInfoAtExistingPath:tmp]; } if (info) { // direct match (a bundle was specified in the path) NSArray *providedResources; NSDictionary *provides; provides = [(NSDictionary *)info objectForKey:@"provides"]; providedResources = [provides objectForKey:_resourceType]; info = nil; if (providedResources == nil) continue; [result addObjectsFromArray:providedResources]; } } } return [result allObjects]; } - (NSBundle *)bundleProvidingResourceOfType:(NSString *)_resourceType matchingQualifier:(EOQualifier *)_qual { NSFileManager *fm = [NSFileManager defaultManager]; NSEnumerator *e; NSString *path; /* foreach search path entry */ e = [self->bundleSearchPaths objectEnumerator]; while ((path = [e nextObject])) { BOOL isDir = NO; if ([fm fileExistsAtPath:path isDirectory:&isDir]) { NSString *tmp; id info = nil; if (!isDir) continue; /* check whether an appropriate bundle is contained in 'path' */ { NSEnumerator *dir; dir = [[fm directoryContentsAtPath:path] objectEnumerator]; while ((tmp = [dir nextObject])) { NSDictionary *bundleInfo; NSArray *providedResources; NSString *infoPath; tmp = [path stringByAppendingPathComponent:tmp]; infoPath = [self makeBundleInfoPath:tmp]; if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) { if (![fm fileExistsAtPath:infoPath]) continue; bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath]; } bundleInfo = [bundleInfo objectForKey:@"provides"]; providedResources = [bundleInfo objectForKey:_resourceType]; bundleInfo = nil; if (providedResources == nil) continue; providedResources = [providedResources filteredArrayUsingQualifier:_qual]; if ([providedResources count] > 0) return [self bundleWithPath:tmp]; } } /* check for direct match */ tmp = [self makeBundleInfoPath:path]; if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) { if ([fm fileExistsAtPath:tmp]) info = [self _loadBundleInfoAtExistingPath:tmp]; } if (info) { // direct match (a bundle was specified in the path) NSArray *providedResources; NSDictionary *provides; provides = [(NSDictionary *)info objectForKey:@"provides"]; providedResources = [provides objectForKey:_resourceType]; info = nil; if (providedResources == nil) continue; providedResources = [providedResources filteredArrayUsingQualifier:_qual]; if ([providedResources count] > 0) return [self bundleWithPath:path]; } } } return nil; } - (NSBundle *)bundlesProvidingResourcesOfType:(NSString *)_resourceType matchingQualifier:(EOQualifier *)_qual { NSMutableArray *bundles = nil; NSFileManager *fm = [NSFileManager defaultManager]; NSEnumerator *e; NSString *path; bundles = [NSMutableArray arrayWithCapacity:128]; /* foreach search path entry */ e = [self->bundleSearchPaths objectEnumerator]; while ((path = [e nextObject])) { BOOL isDir = NO; if ([fm fileExistsAtPath:path isDirectory:&isDir]) { NSString *tmp; id info = nil; if (!isDir) continue; /* check whether an appropriate bundle is contained in 'path' */ { NSEnumerator *dir; dir = [[fm directoryContentsAtPath:path] objectEnumerator]; while ((tmp = [dir nextObject])) { NSDictionary *bundleInfo = nil; NSArray *providedResources = nil; NSString *infoPath; tmp = [path stringByAppendingPathComponent:tmp]; infoPath = [self makeBundleInfoPath:tmp]; if ((bundleInfo=NSMapGet(self->pathToBundleInfo, infoPath)) == nil) { if (![fm fileExistsAtPath:infoPath]) continue; bundleInfo = [self _loadBundleInfoAtExistingPath:infoPath]; } bundleInfo = [bundleInfo objectForKey:@"provides"]; providedResources = [bundleInfo objectForKey:_resourceType]; bundleInfo = nil; if (providedResources == nil) continue; providedResources = [providedResources filteredArrayUsingQualifier:_qual]; if ([providedResources count] > 0) [bundles addObject:[self bundleWithPath:tmp]]; } } /* check for direct match */ tmp = [self makeBundleInfoPath:path]; if ((info = NSMapGet(self->pathToBundleInfo, tmp)) == nil) { if ([fm fileExistsAtPath:tmp]) info = [self _loadBundleInfoAtExistingPath:tmp]; } if (info) { // direct match (a bundle was specified in the path) NSArray *providedResources; NSDictionary *provides; provides = [(NSDictionary *)info objectForKey:@"provides"]; providedResources = [provides objectForKey:_resourceType]; info = nil; if (providedResources == nil) continue; providedResources = [providedResources filteredArrayUsingQualifier:_qual]; if ([providedResources count] > 0) [bundles addObject:[self bundleWithPath:path]]; } } } return [[bundles copy] autorelease]; } // notifications - (void)_bundleDidLoadNotifcation:(NSNotification *)_notification { NSDictionary *ui = [_notification userInfo]; #if 0 NSLog(@"bundle %@ did load with classes %@", [[_notification object] bundlePath], [ui objectForKey:@"NSLoadedClasses"]); #endif [self registerBundle:[_notification object] classes:[ui objectForKey:@"NSLoadedClasses"] categories:[ui objectForKey:@"NSLoadedCategories"]]; } @end /* NGBundleManager */ @implementation NSBundle(BundleManagerSupport) + (id)alloc { return [NGBundle alloc]; } + (id)allocWithZone:(NSZone *)zone { return [NGBundle allocWithZone:zone]; } #if !(NeXT_Foundation_LIBRARY || APPLE_Foundation_LIBRARY) //#warning remember, bundleForClass is not overridden ! #if 0 + (NSBundle *)bundleForClass:(Class)aClass { return [[NGBundleManager defaultBundleManager] bundleForClass:aClass]; } #endif + (NSBundle *)bundleWithPath:(NSString*)path { return [[NGBundleManager defaultBundleManager] bundleWithPath:path]; } #endif @end /* NSBundle(BundleManagerSupport) */ @implementation NSBundle(NGBundleManagerExtensions) - (id)principalObject { return [[NGBundleManager defaultBundleManager] principalObjectOfBundle:self]; } - (NSArray *)providedResourcesOfType:(NSString *)_resourceType { return [[NGBundleManager defaultBundleManager] providedResourcesOfType:_resourceType inBundle:self]; } - (NSString *)bundleName { return [[[self bundlePath] lastPathComponent] stringByDeletingPathExtension]; } - (NSString *)bundleType { return [[self bundlePath] pathExtension]; } - (NSArray *)providedClasses { return [[NGBundleManager defaultBundleManager] classesProvidedByBundle:self]; } - (NSArray *)requiredClasses { return [[NGBundleManager defaultBundleManager] classesRequiredByBundle:self]; } - (NSArray *)requiredBundles { return [[NGBundleManager defaultBundleManager] bundlesRequiredByBundle:self]; } - (NSDictionary *)configForResource:(id)_resource ofType:(NSString *)_type { return [[NGBundleManager defaultBundleManager] configForResource:_resource ofType:_type providedByBundle:self]; } // loading - (BOOL)_loadForBundleManager:(NGBundleManager *)_manager { return [self load]; } @end /* NSBundle(NGBundleManagerExtensions) */ @implementation NSBundle(NGLanguageResourceExtensions) // locating resources - (NSString *)pathForResource:(NSString *)_name ofType:(NSString *)_ext inDirectory:(NSString *)_directory languages:(NSArray *)_languages { NSFileManager *fm = [NSFileManager defaultManager]; NSString *path = nil; int i, langCount; id (*objAtIdx)(id,SEL,int); path = _directory ? [[self bundlePath] stringByAppendingPathComponent:_directory] : [self bundlePath]; if (![fm fileExistsAtPath:path]) return nil; if (_ext) _name = [_name stringByAppendingPathExtension:_ext]; langCount = [_languages count]; objAtIdx = (langCount > 0) ? (void*)[_languages methodForSelector:@selector(objectAtIndex:)] : NULL; for (i = 0; i < langCount; i++) { NSString *language; NSString *lpath; language = objAtIdx ? objAtIdx(_languages, @selector(objectAtIndex:), i) : [_languages objectAtIndex:i]; language = [language stringByAppendingPathExtension:@"lproj"]; lpath = [path stringByAppendingPathComponent:language]; lpath = [lpath stringByAppendingPathComponent:_name]; if ([fm fileExistsAtPath:lpath]) return lpath; } /* now look into x.bundle/Resources/name.type */ if ([fm fileExistsAtPath:[path stringByAppendingPathComponent:_name]]) return [path stringByAppendingPathComponent:_name]; return nil; } - (NSString *)pathForResource:(NSString *)_name ofType:(NSString *)_ext languages:(NSArray *)_languages { NSString *path; path = [self pathForResource:_name ofType:_ext inDirectory:@"Resources" languages:_languages]; if (path) return path; path = [self pathForResource:_name ofType:_ext inDirectory:nil languages:_languages]; return path; } @end /* NSBundle(NGLanguageResourceExtensions) */ @implementation NGBundle + (id)alloc { return [self allocWithZone:NULL]; } + (id)allocWithZone:(NSZone*)zone { return NSAllocateObject(self, 0, zone); } - (id)initWithPath:(NSString *)__path { return [super initWithPath:__path]; } /* loading */ - (BOOL)_loadForBundleManager:(NGBundleManager *)_manager { return [super load]; } - (BOOL)load { NGBundleManager *bm; bm = [NGBundleManager defaultBundleManager]; return [bm loadBundle:self] ? YES : NO; } + (NSBundle *)bundleForClass:(Class)aClass { return [[NGBundleManager defaultBundleManager] bundleForClass:aClass]; } + (NSBundle *)bundleWithPath:(NSString*)path { return [[NGBundleManager defaultBundleManager] bundleWithPath:path]; } #if GNUSTEP_BASE_LIBRARY - (Class)principalClass { Class c; NSString *cname; if ((c = [super principalClass])) return c; if ((cname = [[self infoDictionary] objectForKey:@"NSPrincipalClass"]) ==nil) return Nil; if ((c = NSClassFromString(cname))) return c; NSLog(@"%s: did not find principal class named '%@' of bundle %@", __PRETTY_FUNCTION__, cname, self); return Nil; } /* description */ - (NSString *)description { char buffer[1024]; sprintf (buffer, "<%s %p fullPath: %s infoDictionary: %p loaded=%s>", (char*)object_get_class_name(self), self, [[self bundlePath] cString], [self infoDictionary], self->_codeLoaded ? "yes" : "no"); return [NSString stringWithCString:buffer]; } #endif @end /* NGBundle */