Add placeholder for types that start with A, for _Atomic() types.
[class-dump.git] / Source / CDMultiFileVisitor.m
blobfc216296b254d8abaf8a05cd53e2396a553ed0fb
1 // -*- mode: ObjC -*-
3 //  This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files.
4 //  Copyright (C) 1997-2019 Steve Nygard.
6 #import "CDMultiFileVisitor.h"
8 #import "CDClassDump.h"
9 #import "CDClassFrameworkVisitor.h"
10 #import "CDOCCategory.h"
11 #import "CDOCClass.h"
12 #import "CDOCProtocol.h"
13 #import "CDOCInstanceVariable.h"
14 #import "CDTypeController.h"
16 @interface CDMultiFileVisitor ()
18 // NSString (class name) -> NSString (framework name)
19 @property (strong) NSDictionary *frameworkNamesByClassName;
21 // NSString (protocol name) -> NSString (framework name)
22 @property (strong) NSDictionary *frameworkNamesByProtocolName;
24 // Location in output string to insert the protocol imports and forward class declarations.
25 // We don't know what classes and protocols will be referenced until the rest of the output is generated.
26 @property (assign) NSUInteger referenceLocation;
28 // Class and protocol references
29 @property (readonly) NSMutableSet *referencedClassNames;
30 @property (readonly) NSMutableSet *referencedProtocolNames;
31 @property (readonly) NSMutableSet *weaklyReferencedProtocolNames; // Protocols that can be forward-declared instead of imported
33 @property (nonatomic, readonly) NSArray *referencedClassNamesSortedByName;
34 @property (nonatomic, readonly) NSArray *referencedProtocolNamesSortedByName;
35 @property (nonatomic, readonly) NSArray *weaklyReferencedProtocolNamesSortedByName;
37 @property (nonatomic, readonly) NSString *referenceString;
39 @end
41 #pragma mark -
43 @implementation CDMultiFileVisitor
45     NSString *_outputPath;
46     
47     NSDictionary *_frameworkNamesByClassName;
48     NSMutableSet *_referencedClassNames;
49     NSMutableSet *_referencedProtocolNames;
50     NSUInteger _referenceLocation;
53 - (id)init;
55     if ((self = [super init])) {
56         _referencedClassNames = [[NSMutableSet alloc] init];
57         _referencedProtocolNames = [[NSMutableSet alloc] init];
58         _weaklyReferencedProtocolNames = [[NSMutableSet alloc] init];
59     }
60     
61     return self;
64 #pragma mark -
66 - (void)willBeginVisiting;
68     [super willBeginVisiting];
70     [self.classDump appendHeaderToString:self.resultString];
72     if (self.classDump.hasObjectiveCRuntimeInfo) {
73         [self buildClassFrameworks];
74         [self createOutputPathIfNecessary];
75         [self generateStructureHeader];
76     } else {
77         // TODO: (2007-06-14) Make sure this generates no output files in this case.
78         NSLog(@"Warning: This file does not contain any Objective-C runtime information.");
79     }
82 - (void)willVisitClass:(CDOCClass *)aClass;
84     // First, we set up some context...
85     [self.resultString setString:@""];
86     [self.classDump appendHeaderToString:self.resultString];
88     [self removeAllClassNameProtocolNameReferences];
89     NSString *str = [self importStringForClassName:aClass.superClassName];
90     if (str != nil) {
91         [self.resultString appendString:str];
92         [self.resultString appendString:@"\n"];
93     }
95     self.referenceLocation = [self.resultString length];
97     // And then generate the regular output
98     [super willVisitClass:aClass];
99     
100     [self addReferencesToProtocolNamesInArray:aClass.protocolNames];
103 - (void)didVisitClass:(CDOCClass *)aClass;
105     // Generate the regular output
106     [super didVisitClass:aClass];
108     // Then insert the imports and write the file.
109     [self removeReferenceToClassName:aClass.name];
110     [self removeReferenceToClassName:aClass.superClassName];
111     NSString *referenceString = self.referenceString;
112     if (referenceString != nil)
113         [self.resultString insertString:referenceString atIndex:self.referenceLocation];
115     NSString *filename = [NSString stringWithFormat:@"%@.h", aClass.name];
116     if (self.outputPath != nil)
117         filename = [self.outputPath stringByAppendingPathComponent:filename];
119     [[self.resultString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filename atomically:YES];
122 - (void)willVisitCategory:(CDOCCategory *)category;
124     // First, we set up some context...
125     [self.resultString setString:@""];
126     [self.classDump appendHeaderToString:self.resultString];
128     [self removeAllClassNameProtocolNameReferences];
129     NSString *str = [self importStringForClassName:category.className];
130     if (str != nil) {
131         [self.resultString appendString:str];
132         [self.resultString appendString:@"\n"];
133     }
134     self.referenceLocation = [self.resultString length];
136     // And then generate the regular output
137     [super willVisitCategory:category];
139     [self addReferencesToProtocolNamesInArray:category.protocolNames];
142 - (void)didVisitCategory:(CDOCCategory *)category;
144     // Generate the regular output
145     [super didVisitCategory:category];
147     // Then insert the imports and write the file.
148     [self removeReferenceToClassName:category.className];
149     NSString *referenceString = self.referenceString;
150     if (referenceString != nil)
151         [self.resultString insertString:referenceString atIndex:self.referenceLocation];
153     NSString *filename = [NSString stringWithFormat:@"%@-%@.h", category.className, category.name];
154     if (self.outputPath != nil)
155         filename = [self.outputPath stringByAppendingPathComponent:filename];
157     [[self.resultString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filename atomically:YES];
160 - (void)willVisitProtocol:(CDOCProtocol *)protocol;
162     [self.resultString setString:@""];
163     [self.classDump appendHeaderToString:self.resultString];
165     [self removeAllClassNameProtocolNameReferences];
166     self.referenceLocation = [self.resultString length];
168     // And then generate the regular output
169     [super willVisitProtocol:protocol];
171     [self addReferencesToProtocolNamesInArray:protocol.protocolNames];
174 - (void)didVisitProtocol:(CDOCProtocol *)protocol;
176     // Generate the regular output
177     [super didVisitProtocol:protocol];
179     // Then insert the imports and write the file.
180     NSString *referenceString = self.referenceString;
181     if (referenceString != nil)
182         [self.resultString insertString:referenceString atIndex:self.referenceLocation];
184     NSString *filename = [NSString stringWithFormat:@"%@-Protocol.h", protocol.name];
185     if (self.outputPath != nil)
186         filename = [self.outputPath stringByAppendingPathComponent:filename];
188     [[self.resultString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filename atomically:YES];
191 #pragma mark - CDTypeControllerDelegate
193 - (void)typeController:(CDTypeController *)typeController didReferenceClassName:(NSString *)name;
195     [self addReferenceToClassName:name];
198 - (void)typeController:(CDTypeController *)typeController didReferenceProtocolNames:(NSArray *)names;
200     [self addWeakReferencesToProtocolNamesInArray:names];
203 #pragma mark -
205 - (NSString *)frameworkForClassName:(NSString *)name;
207     NSString *framework = self.frameworkNamesByClassName[name];
208     
209     // Map public CoreFoundation classes to Foundation, because that is where the headers are exposed
210     if ([framework isEqualToString:@"CoreFoundation"] && [name hasPrefix:@"NS"]) {
211         framework = @"Foundation";
212     }
213     
214     return framework;
217 - (NSString *)frameworkForProtocolName:(NSString *)name;
219     return self.frameworkNamesByProtocolName[name];
222 - (NSString *)importStringForClassName:(NSString *)name;
224     if (name != nil) {
225         NSString *framework = [self frameworkForClassName:name];
226         if (framework == nil)
227             return [NSString stringWithFormat:@"#import \"%@.h\"\n", name];
228         else
229             return [NSString stringWithFormat:@"#import <%@/%@.h>\n", framework, name];
230     }
231     
232     return nil;
235 - (NSString *)importStringForProtocolName:(NSString *)name;
237     if (name != nil) {
238         NSString *framework = [self frameworkForProtocolName:name];
239         NSString *headerName = [name stringByAppendingString:@"-Protocol.h"];
240         if (framework == nil)
241             return [NSString stringWithFormat:@"#import \"%@\"\n", headerName];
242         else
243             return [NSString stringWithFormat:@"#import <%@/%@>\n", framework, headerName];
244     }
245     
246     return nil;
249 #pragma mark - Class and Protocol name tracking
251 - (NSArray *)referencedClassNamesSortedByName;
253     return [[self.referencedClassNames allObjects] sortedArrayUsingSelector:@selector(compare:)];
256 - (NSArray *)referencedProtocolNamesSortedByName;
258     return [[self.referencedProtocolNames allObjects] sortedArrayUsingSelector:@selector(compare:)];
261 - (NSArray *)weaklyReferencedProtocolNamesSortedByName;
263     return [[self.weaklyReferencedProtocolNames allObjects] sortedArrayUsingSelector:@selector(compare:)];
266 - (void)addReferenceToClassName:(NSString *)className;
268     [self.referencedClassNames addObject:className];
271 - (void)removeReferenceToClassName:(NSString *)className;
273     if (className != nil)
274         [self.referencedClassNames removeObject:className];
277 - (void)addReferencesToProtocolNamesInArray:(NSArray *)protocolNames;
279     [self.referencedProtocolNames addObjectsFromArray:protocolNames];
282 - (void)addWeakReferencesToProtocolNamesInArray:(NSArray *)protocolNames;
284     [self.weaklyReferencedProtocolNames addObjectsFromArray:protocolNames];
287 - (void)removeAllClassNameProtocolNameReferences;
289     [self.referencedClassNames removeAllObjects];
290     [self.referencedProtocolNames removeAllObjects];
291     [self.weaklyReferencedProtocolNames removeAllObjects];
294 #pragma mark -
296 - (void)createOutputPathIfNecessary;
298     if (self.outputPath != nil) {
299         BOOL isDirectory;
300         
301         NSFileManager *fileManager = [NSFileManager defaultManager];
302         if ([fileManager fileExistsAtPath:self.outputPath isDirectory:&isDirectory] == NO) {
303             NSError *error = nil;
304             BOOL result = [fileManager createDirectoryAtPath:self.outputPath withIntermediateDirectories:YES attributes:nil error:&error];
305             if (result == NO) {
306                 NSLog(@"Error: Couldn't create output directory: %@", self.outputPath);
307                 NSLog(@"error: %@", error); // TODO: Test this
308                 return;
309             }
310         } else if (isDirectory == NO) {
311             NSLog(@"Error: File exists at output path: %@", self.outputPath);
312             return;
313         }
314     }
317 #pragma mark -
319 // - imports for each referenced protocol
320 // - forward declarations for each referenced class
322 - (NSString *)referenceString;
324     NSMutableString *referenceString = [[NSMutableString alloc] init];
326     if ([self.referencedProtocolNames count] > 0) {
327         for (NSString *name in self.referencedProtocolNamesSortedByName) {
328             NSString *str = [self importStringForProtocolName:name];
329             if (str != nil)
330                 [referenceString appendString:str];
331         }
333         [referenceString appendString:@"\n"];
334     }
335     
336     BOOL addNewline = NO;
337     if ([self.referencedClassNames count] > 0) {
338         [referenceString appendFormat:@"@class %@;\n", [self.referencedClassNamesSortedByName componentsJoinedByString:@", "]];
339         addNewline = YES;
340     }
342     if ([self.weaklyReferencedProtocolNames count] > 0) {
343         [referenceString appendFormat:@"@protocol %@;\n", [self.weaklyReferencedProtocolNamesSortedByName componentsJoinedByString:@", "]];
344         addNewline = YES;
345     }
346     
347     if (addNewline)
348         [referenceString appendString:@"\n"];
349     
350     if ([referenceString length] == 0)
351         return nil;
352     
353     return [referenceString copy];
356 #pragma mark -
358 - (void)buildClassFrameworks;
360     CDClassFrameworkVisitor *visitor = [[CDClassFrameworkVisitor alloc] init];
361     visitor.classDump = self.classDump;
362     
363     [self.classDump recursivelyVisit:visitor];
364     self.frameworkNamesByClassName = visitor.frameworkNamesByClassName;
365     self.frameworkNamesByProtocolName = visitor.frameworkNamesByProtocolName;
368 - (void)generateStructureHeader;
370     [self.resultString setString:@""];
371     [self.classDump appendHeaderToString:self.resultString];
372     
373     [self removeAllClassNameProtocolNameReferences];
374     self.referenceLocation = [self.resultString length];
375     
376     [[self.classDump typeController] appendStructuresToString:self.resultString];
377     
378     NSString *referenceString = [self referenceString];
379     if (referenceString != nil)
380         [self.resultString insertString:referenceString atIndex:self.referenceLocation];
381     
382     NSString *filename = @"CDStructures.h";
383     if (self.outputPath != nil)
384         filename = [self.outputPath stringByAppendingPathComponent:filename];
385     
386     [[self.resultString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filename atomically:YES];
389 @end