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"
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;
43 @implementation CDMultiFileVisitor
45 NSString *_outputPath;
47 NSDictionary *_frameworkNamesByClassName;
48 NSMutableSet *_referencedClassNames;
49 NSMutableSet *_referencedProtocolNames;
50 NSUInteger _referenceLocation;
55 if ((self = [super init])) {
56 _referencedClassNames = [[NSMutableSet alloc] init];
57 _referencedProtocolNames = [[NSMutableSet alloc] init];
58 _weaklyReferencedProtocolNames = [[NSMutableSet alloc] init];
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];
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.");
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];
91 [self.resultString appendString:str];
92 [self.resultString appendString:@"\n"];
95 self.referenceLocation = [self.resultString length];
97 // And then generate the regular output
98 [super willVisitClass:aClass];
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];
131 [self.resultString appendString:str];
132 [self.resultString appendString:@"\n"];
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];
205 - (NSString *)frameworkForClassName:(NSString *)name;
207 NSString *framework = self.frameworkNamesByClassName[name];
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";
217 - (NSString *)frameworkForProtocolName:(NSString *)name;
219 return self.frameworkNamesByProtocolName[name];
222 - (NSString *)importStringForClassName:(NSString *)name;
225 NSString *framework = [self frameworkForClassName:name];
226 if (framework == nil)
227 return [NSString stringWithFormat:@"#import \"%@.h\"\n", name];
229 return [NSString stringWithFormat:@"#import <%@/%@.h>\n", framework, name];
235 - (NSString *)importStringForProtocolName:(NSString *)name;
238 NSString *framework = [self frameworkForProtocolName:name];
239 NSString *headerName = [name stringByAppendingString:@"-Protocol.h"];
240 if (framework == nil)
241 return [NSString stringWithFormat:@"#import \"%@\"\n", headerName];
243 return [NSString stringWithFormat:@"#import <%@/%@>\n", framework, headerName];
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];
296 - (void)createOutputPathIfNecessary;
298 if (self.outputPath != nil) {
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];
306 NSLog(@"Error: Couldn't create output directory: %@", self.outputPath);
307 NSLog(@"error: %@", error); // TODO: Test this
310 } else if (isDirectory == NO) {
311 NSLog(@"Error: File exists at output path: %@", self.outputPath);
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];
330 [referenceString appendString:str];
333 [referenceString appendString:@"\n"];
336 BOOL addNewline = NO;
337 if ([self.referencedClassNames count] > 0) {
338 [referenceString appendFormat:@"@class %@;\n", [self.referencedClassNamesSortedByName componentsJoinedByString:@", "]];
342 if ([self.weaklyReferencedProtocolNames count] > 0) {
343 [referenceString appendFormat:@"@protocol %@;\n", [self.weaklyReferencedProtocolNamesSortedByName componentsJoinedByString:@", "]];
348 [referenceString appendString:@"\n"];
350 if ([referenceString length] == 0)
353 return [referenceString copy];
358 - (void)buildClassFrameworks;
360 CDClassFrameworkVisitor *visitor = [[CDClassFrameworkVisitor alloc] init];
361 visitor.classDump = self.classDump;
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];
373 [self removeAllClassNameProtocolNameReferences];
374 self.referenceLocation = [self.resultString length];
376 [[self.classDump typeController] appendStructuresToString:self.resultString];
378 NSString *referenceString = [self referenceString];
379 if (referenceString != nil)
380 [self.resultString insertString:referenceString atIndex:self.referenceLocation];
382 NSString *filename = @"CDStructures.h";
383 if (self.outputPath != nil)
384 filename = [self.outputPath stringByAppendingPathComponent:filename];
386 [[self.resultString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filename atomically:YES];