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