|
|
1.1 root 1: /*
2: * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
3: *
4: * @APPLE_LICENSE_HEADER_START@
5: *
6: * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
7: * Reserved. This file contains Original Code and/or Modifications of
8: * Original Code as defined in and that are subject to the Apple Public
9: * Source License Version 1.0 (the 'License'). You may not use this file
10: * except in compliance with the License. Please obtain a copy of the
11: * License at http://www.apple.com/publicsource and read it before using
12: * this file.
13: *
14: * The Original Code and all software distributed under the License are
15: * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16: * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17: * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
19: * License for the specific language governing rights and limitations
20: * under the License."
21: *
22: * @APPLE_LICENSE_HEADER_END@
23: */
24: /* NXBundle.m
25: Copyright 1990, 1991, NeXT, Inc.
26: IPC, November 1990
27: */
28:
29: #ifndef KERNEL
30: #ifdef SHLIB
31: #import "shlib.h"
32: #endif SHLIB
33:
34:
35: #import "NXBundle.h"
36: #import "NXBundlePrivate.h"
37: #import "maptable.h"
38: #import "NXStringTable.h"
39: #import "objc-load.h"
40: #import "hashtable.h"
41: #import "objc-private.h"
42:
43: #import <sys/file.h>
44: #import <sys/dir.h>
45: #import <sys/stat.h>
46: #import <sys/types.h>
47:
48: #import <strings.h>
49:
50: static void warning(const char *format, ...) {}
51:
52: /* For the LProj cache */
53: static id bundleTable = nil;
54: static id mainBundleStringTableTable = nil;
55:
56: #define BUNDLE_SUFFIX "bundle"
57:
58: /******* Bundle ********/
59:
60: @implementation NXBundle
61:
62: static NXMapTable *classToBundle = NULL;
63:
64: static NXBundle *mainBundle = nil;
65: static NXMapTable *pathToBundle = NULL;
66:
67: - initForDirectory:(const char *)bp {
68: struct stat statInfo;
69: id bb;
70:
71: if (! pathToBundle) pathToBundle = NXCreateMapTable(NXStrValueMapPrototype, 1);
72: bb = NXMapGet(pathToBundle, bp);
73: if (bb) {
74: [self free];
75: return bb;
76: } else {
77: BOOL allOK = YES;
78:
79: if (stat(bp, &statInfo) == 0) {
80: while (allOK && (statInfo.st_mode & S_IFLNK)) {
81: char linkPath[MAXPATHLEN + 1];
82:
83: allOK = ((readlink(bp, linkPath, sizeof(linkPath)) != -1)
84: && (stat(linkPath, &statInfo) == 0));
85: }
86: allOK = allOK
87: && ((statInfo.st_mode & (S_IFDIR | S_IREAD | S_IEXEC))
88: ? YES : NO);
89: }
90: if (allOK) {
91: _directory = NXCopyStringBuffer(bp);
92: _codeLoaded = NO;
93: NXMapInsert(pathToBundle, _directory, self);
94: return self;
95: } else {
96: [self free];
97: return nil;
98: }
99: }
100: }
101:
102: - free
103: {
104: if (_codeLoaded)
105: return self;
106: else {
107: NXMapRemove(pathToBundle, _directory);
108: free(_directory);
109: return [super free];
110: }
111: }
112:
113: + mainBundle {
114: if (! mainBundle) {
115: char prog[MAXPATHLEN];
116: char *last;
117: char **argv;
118: #if defined(__DYNAMIC__)
119: extern char ***_NSGetArgv();
120: argv = *_NSGetArgv();
121: #else
122: extern char **NXArgv;
123: argv = NXArgv;
124: #endif
125: if (argv[0][0] != '/') { getwd(prog); strcat(prog, "/"); }
126: else prog[0] = 0;
127: strcat(prog, argv[0]);
128: last = rindex(prog, '/');
129: last[0] = 0;
130: mainBundle = [[self alloc] initForDirectory: prog];
131: mainBundle->_codeLoaded = YES;
132: }
133: return mainBundle;
134: }
135:
136: + bundleForClass:class {
137: id bundle;
138: if (! classToBundle) return [self mainBundle];
139: bundle = NXMapGet(classToBundle, class);
140: return (bundle) ? bundle : [self mainBundle];
141: }
142:
143: - (const char *)directory {
144: return _directory;
145: }
146:
147: static Class globalPrincipalClass = NULL;
148: static id globalBundle = nil;
149:
150: static void loadCallback(Class cls, Category cat ) {
151: warning("Loading %s '%s'\n", (cat) ? "category of" : "class", cls->name);
152: if (cat) return;
153: if (! classToBundle) classToBundle = NXCreateMapTable(NXPtrValueMapPrototype, 1);
154: NXMapInsert(classToBundle, cls, globalBundle);
155: if (! globalPrincipalClass) globalPrincipalClass = cls;
156: }
157:
158: static NXStream* bundleErrorStream = 0;
159:
160: + (NXStream*) setErrorStream:(NXStream*)stream
161: {
162: NXStream* s = bundleErrorStream;
163: bundleErrorStream = stream;
164: return s;
165: }
166:
167: - (BOOL)ensureLoaded {
168: if (! _codeLoaded) {
169: char name[MAXPATHLEN];
170: char path[MAXPATHLEN];
171: char *moduleList[2] = {path, NULL};
172: NXStream *errorStream;
173: long err;
174: char *slash, *dot;
175:
176: globalPrincipalClass = NULL;
177: globalBundle = self;
178: slash = rindex(_directory, '/');
179: if (slash) {
180: strcpy(name, slash + 1);
181: } else {
182: strcpy(name, _directory);
183: }
184: dot = rindex(name, '.');
185: if (dot)
186: *dot = '\0';
187: if ([self getPath: path forResource: name ofType: NULL]) {
188: errorStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
189:
190: err = objc_loadModules(moduleList, errorStream, loadCallback,
191: NULL, NULL);
192: if (err) {
193: char *streamBuf;
194: int len, maxLen;
195:
196: NXGetMemoryBuffer(errorStream, &streamBuf, &len, &maxLen);
197: if (!bundleErrorStream)
198: {
199: _NXLogError("Error loading %s", path);
200: if (len) {
201: streamBuf[len] = 0;
202: _NXLogError(streamBuf);
203: }
204: _NXLogError("\n");
205: }
206: else
207: {
208: NXPrintf (bundleErrorStream, "Error loading %s", path);
209: if (len) {
210: streamBuf[len] = 0;
211: NXPrintf (bundleErrorStream, streamBuf);
212: }
213: NXPrintf (bundleErrorStream, "\n");
214: }
215: } else {
216: _principalClass = globalPrincipalClass;
217: _codeLoaded = YES;
218: }
219: NXCloseMemory(errorStream, NX_FREEBUFFER|NX_TRUNCATEBUFFER);
220: if (err) return NO;
221: } else {
222: if (! bundleErrorStream)
223: _NXLogError("Couldn't find %s in bundle for %s", name, _directory);
224: else
225: NXPrintf (bundleErrorStream,
226: "Couldn't find %s in bundle for %s", name, _directory);
227: return NO;
228: }
229:
230: }
231: return YES;
232: }
233:
234: - classNamed:(const char *)className {
235: if (![self ensureLoaded]) return nil;
236: return objc_getClass((char *)className);
237: }
238:
239: - principalClass {
240: if (![self ensureLoaded]) return nil;
241: return (id)_principalClass;
242: }
243:
244: - setVersion:(int)version {
245: _bundleVersion = version;
246: return self;
247: }
248:
249: - (int)version {
250: return _bundleVersion;
251: }
252:
253: /******* International Routines ********/
254:
255: static BOOL systemHasLanguages = YES;
256: static const char *const *systemLanguages = NULL;
257:
258: static const char *const *getDefaultSystemLanguages(void) {
259: int count, length;
260: const char *language, *separator;
261: char **defaultSystemLanguages = NULL;
262:
263: if (systemHasLanguages) {
264: language = "English";
265: if (language && *language) {
266: count = 1;
267: separator = strchr(language, ';');
268: while (separator) {
269: count++;
270: separator = strchr(separator+1, ';');
271: }
272: defaultSystemLanguages = malloc((count + 1) * sizeof(char *));
273: defaultSystemLanguages[count] = NULL;
274: for (count = 0; language; count++) {
275: separator = strchr(language, ';');
276: if (separator) {
277: length = separator - language;
278: separator++;
279: } else {
280: length = strlen(language);
281: }
282: while (*language == ' ' || *language == '\t') {
283: language++; length--;
284: }
285: while (language[length-1] == ' ' || language[length-1] == '\t') length--;
286: defaultSystemLanguages[count] = malloc((length + 1) * sizeof(char));
287: strncpy(defaultSystemLanguages[count], language, length);
288: defaultSystemLanguages[count][length] = '\0';
289: language = separator;
290: }
291: } else {
292: systemHasLanguages = NO;
293: }
294: }
295:
296: return defaultSystemLanguages;
297: }
298:
299: #ifdef DEBUG
300: static int ACCESS(const char *path, int way)
301: {
302: int retval = access(path, way);
303: printf("access(%s, R_OK) returns %d\n", path, retval);
304: return retval;
305: }
306: #else
307: #define ACCESS access
308: #endif
309:
310: #define LINE_BUFFER_SIZE 256
311:
312: /* The passed-in path is the .lproj directory to look in for the version. It's not clear what the best way to encode a version into an .lproj directory, but this is probably as good as any...it parses through a file named "version" (if found) looking for an integer value starting a line. If it finds one, that's the version. If no version file exists in the .lproj directory, then its version is 0. */
313:
314: static int readLprojVersion(char *path) {
315: FILE *file;
316: int version = 0;
317: int pathLength = strlen(path);
318:
319: strcat(path, "/version");
320: file = fopen(path, "r");
321: if (file) {
322: char *eof;
323: char lineBuffer[LINE_BUFFER_SIZE+1];
324: *lineBuffer = '\0';
325: do {
326: eof = fgets(lineBuffer, LINE_BUFFER_SIZE, file);
327: version = atoi(lineBuffer);
328: } while (!eof && !version);
329: }
330: path[pathLength] = '\0';
331: fclose(file);
332:
333: return version;
334: }
335:
336: /* Adds the passed extension to the passed name iff the extension is not already on that name. */
337:
338: static void addExtension(char *name, const char *extension)
339: {
340: if (extension) {
341: const char *existingExtension = strrchr(name, '.');
342: if (existingExtension && extension[0] != '.') existingExtension++;
343: if (!existingExtension || strcmp(existingExtension, extension)) {
344: if (extension[0] != '.' && extension[0]) strcat(name, ".");
345: strcat(name, extension);
346: }
347: }
348: }
349:
350: /* This function takes a string and does a readdir() on the passed directory and adds the names of all the files in the directory to that string. Returns a potentially newly realloc()'ed version of the string. */
351:
352: static char *addDirectoryEntriesTo(char *files, const char *directory)
353: {
354: DIR *dirp;
355: struct direct *dp;
356: int length;
357:
358: length = strlen(files);
359: if ((dirp = opendir(directory))) {
360: dp = readdir(dirp); /* Skip . */
361: dp = readdir(dirp); /* Skip .. */
362: for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
363: files = realloc(files, length + dp->d_namlen + 2);
364: strncpy(files + length, dp->d_name, dp->d_namlen);
365: length += dp->d_namlen;
366: files[length++] = '/';
367: }
368: files[length] = '\0';
369: closedir(dirp);
370: }
371:
372: return files;
373: }
374:
375: /* There's a little bit of an efficiency hack in this function. The NXMapTable lprojTable is a map table with keys and values both null-terminated strings. The keys are the path up to and including the .lproj directory. The value is of the form "version/file1/file2/file3/file4". atoi() is used to suck the version out of the front. */
376:
377: static BOOL isFileInValidLproj(char *path, const char *name, const char *extension, int bundleVersion)
378: {
379: char *lprojInfo, *slash;
380: int length, version = -1;
381: char buffer[MAXPATHLEN+1];
382: static NXMapTable *lprojTable = NULL;
383:
384: if (!lprojTable) lprojTable = NXCreateMapTable(NXStrValueMapPrototype, 0);
385:
386: lprojInfo = NXMapGet(lprojTable, path);
387:
388: if (!lprojInfo) {
389: if (ACCESS(path, R_OK) >= 0) version = readLprojVersion(path);
390: sprintf(buffer, "%d/", version);
391: lprojInfo = NXCopyStringBuffer(buffer);
392: lprojInfo = addDirectoryEntriesTo(lprojInfo, path);
393: NXMapInsert(lprojTable, NXCopyStringBuffer(path), lprojInfo);
394: } else {
395: version = atoi(lprojInfo);
396: }
397:
398: if (version == bundleVersion && (lprojInfo = strchr(lprojInfo, '/'))) {
399: strcpy(buffer, name);
400: slash = buffer;
401: while (*slash == '/') slash++;
402: if ((slash = strchr(buffer, '/'))) {
403: *(slash+1) = '\0';
404: } else {
405: addExtension(buffer, extension);
406: strcat(buffer, "/");
407: }
408: length = strlen(buffer);
409: while (*++lprojInfo) {
410: if (!strncmp(lprojInfo, buffer, length)) {
411: if (path[strlen(path)-1] != '/') strcat(path, "/");
412: strcat(path, name);
413: addExtension(path, extension);
414: return slash ? (ACCESS(path, R_OK) >= 0) : YES;
415: }
416: lprojInfo = strchr(lprojInfo, '/');
417: if (!lprojInfo) return NO; /* should never happen */
418: }
419: }
420:
421: return NO;
422: }
423:
424: /* Returns a path to the appropriate shadow .lproj in /NextLibrary/Languages. */
425:
426: static BOOL getPathToNextLibraryLanguages(char *path, const char *realPath, const char *language)
427: {
428: const char *s, *t;
429: char scratch[ MAXPATHLEN+1 ];
430:
431: s = strchr(realPath, '.');
432: while (s) {
433: if (!strncmp(s, ".app", 4)) {
434: char *p;
435: strcpy(scratch, realPath);
436: p = scratch + (s - realPath);
437: t = p+4;
438: if (*t == '/') t++;
439: *p = '\0';
440: s = strrchr(scratch, '/');
441: if (s) {
442: if (*t) {
443: sprintf(path, "/NextLibrary/Languages/%s%s.lproj/%s", language, s, t);
444: } else {
445: sprintf(path, "/NextLibrary/Languages/%s%s.lproj", language, s);
446: }
447: }
448: return YES;
449: } else {
450: s = strchr(s+1, '.');
451: }
452: }
453: if (!s && !strncmp(realPath, "/usr/lib/", 9)) {
454: s = realPath+8;
455: while (*s == '/') s++;
456: if (*s) {
457: s = strchr(s, '/');
458: if (s) {
459: char *tmp;
460:
461: strcpy(scratch, realPath);
462: tmp = scratch + (s - realPath);
463: *tmp++ = '\0';
464: s = tmp;
465: }
466: if (s && *s) {
467: sprintf(path, "/NextLibrary/Languages/%s%s.lproj/%s", language, scratch+8, s);
468: } else {
469: sprintf(path, "/NextLibrary/Languages/%s%s.lproj", language, realPath+8);
470: }
471: return YES;
472: }
473: }
474:
475: return NO;
476: }
477:
478: + (BOOL)getPath:(char *)path forResource:(const char *)name
479: ofType:(const char *)ext inDirectory: (const char *)bundlePath
480: withVersion: (int)version
481: {
482: const char *const *cur;
483: char searchPath[MAXPATHLEN+1];
484:
485: if (!name) return NO;
486: if (!path) path = searchPath;
487:
488: if (!systemLanguages) systemLanguages = getDefaultSystemLanguages();
489:
490: if (systemLanguages) {
491: for (cur = systemLanguages; *cur; cur++) {
492: /* First look for a language in the bundle. */
493: if (bundlePath) {
494: sprintf(path, "%s/%s.lproj", bundlePath, *cur);
495: if (isFileInValidLproj(path, name, ext, version)) return YES;
496: }
497: /* Then look in /NextLibrary/Languages. */
498: if (getPathToNextLibraryLanguages(path, bundlePath, *cur)) {
499: if (isFileInValidLproj(path, name, ext, version)) return YES;
500: }
501: }
502: }
503:
504: sprintf(path, "%s", bundlePath);
505: if (isFileInValidLproj(path, name, ext, version)) return YES;
506:
507: return NO;
508: }
509:
510: -(BOOL)getPath: (char *)path forResource: (const char *)name
511: ofType: (const char *)ext
512: {
513: return [[self class] getPath: path forResource: name ofType: ext
514: inDirectory: _directory withVersion: _bundleVersion];
515: }
516:
517: + setSystemLanguages:(const char *const *)languageList {
518: systemLanguages = languageList;
519: if (!languageList) systemHasLanguages = YES;
520: return self;
521: }
522:
523: @end
524:
525: const char *NXLoadLocalizedStringFromTableInBundle(const char *table, NXBundle *bundle, const char *key, const char *value)
526: {
527: static NXZone *LocalizedStringZone = NULL;
528:
529: #define LSZONE (LocalizedStringZone ? LocalizedStringZone : (LocalizedStringZone = NXDefaultMallocZone()))
530:
531: const char *tableValue;
532: id stringTable, stringTableTable;
533: char path[MAXPATHLEN + 1];
534:
535: if (!key) return value ? value : "";
536: if (!bundle) bundle = [NXBundle mainBundle];
537: if (!table) table = "Localizable";
538:
539: if (bundle != [NXBundle mainBundle]) {
540: stringTableTable = [bundleTable valueForKey:bundle];
541: } else {
542: stringTableTable = mainBundleStringTableTable;
543: }
544:
545: if (!(stringTable = [stringTableTable valueForKey:table])) {
546: [bundle getPath:path forResource:table ofType:"strings"];
547: stringTable = [NXStringTable newFromFile: path];
548: if (!stringTableTable) {
549: if (bundle != [NXBundle mainBundle]) {
550: if (!bundleTable) bundleTable = [[HashTable allocFromZone:LSZONE] initKeyDesc:"!"];
551: if (bundleTable) {
552: stringTableTable = [[HashTable allocFromZone:LSZONE] initKeyDesc:"*"];
553: if (stringTableTable) [bundleTable insertKey:bundle value:stringTableTable];
554: }
555: } else {
556: mainBundleStringTableTable = stringTableTable = [[HashTable allocFromZone:LSZONE] initKeyDesc:"*"];
557: }
558: }
559: if (stringTableTable && !stringTable) stringTable = [[NXStringTable allocFromZone:LSZONE] init];
560: if (stringTable) [stringTableTable insertKey:table value:stringTable];
561: }
562:
563: if (!(tableValue = [stringTable valueForStringKey:key])) {
564: tableValue = value ? value : key;
565: [stringTable insertKey:key value:(void *)tableValue];
566: }
567:
568: return tableValue;
569: }
570: const char *NXLoadLocalStringFromTableInBundle(const char *table, NXBundle *bundle, const char *key, const char *value)
571: {
572: return NXLoadLocalizedStringFromTableInBundle(table, bundle, key, value);
573: }
574: @implementation NXBundle (Compatability)
575:
576: - initForPath: (const char *)path { return [self initForDirectory: path]; }
577: - (const char *)path { return [self directory]; }
578: + forClass: class { return [self bundleForClass: class]; }
579: + newFromPath:(const char *)bp { return [[self alloc] initForPath: bp]; }
580: - initFromPath:(const char *)bp { return [self initForPath: bp]; }
581: - (const char *)bundlePath { return [self path]; }
582: - (BOOL)codeLoaded { return _codeLoaded; }
583: - firstClass { return [self principalClass]; }
584: - (BOOL)getResourcePath:(char *)path forName:(const char *)name type:(const char *)ext { return [self getPath: path forResource: name ofType: ext]; }
585: - getClass: (const char *)className { return [self classNamed: className]; }
586: - classForName: (const char *)className { return [self classNamed: className]; }
587:
588: @end
589:
590: @implementation NXBundle (Private)
591:
592: static void noFree(void *ptr) {}
593: static void freeObjects(void *ptr)
594: {
595: [(HashTable *)ptr freeObjects];
596: [(HashTable *)ptr free];
597: }
598:
599: + (void)_invalidateLprojCache
600: {
601: if (bundleTable) {
602: [bundleTable freeKeys: noFree values: freeObjects];
603: [bundleTable free];
604: bundleTable = nil;
605: }
606: if (mainBundleStringTableTable) {
607: [mainBundleStringTableTable freeObjects];
608: [mainBundleStringTableTable free];
609: mainBundleStringTableTable = nil;
610: }
611: }
612:
613: @end
614: #endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.