blob: fc216296b254d8abaf8a05cd53e2396a553ed0fb [file] [log] [blame]
// -*- mode: ObjC -*-
// This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files.
// Copyright (C) 1997-2019 Steve Nygard.
#import "CDMultiFileVisitor.h"
#import "CDClassDump.h"
#import "CDClassFrameworkVisitor.h"
#import "CDOCCategory.h"
#import "CDOCClass.h"
#import "CDOCProtocol.h"
#import "CDOCInstanceVariable.h"
#import "CDTypeController.h"
@interface CDMultiFileVisitor ()
// NSString (class name) -> NSString (framework name)
@property (strong) NSDictionary *frameworkNamesByClassName;
// NSString (protocol name) -> NSString (framework name)
@property (strong) NSDictionary *frameworkNamesByProtocolName;
// Location in output string to insert the protocol imports and forward class declarations.
// We don't know what classes and protocols will be referenced until the rest of the output is generated.
@property (assign) NSUInteger referenceLocation;
// Class and protocol references
@property (readonly) NSMutableSet *referencedClassNames;
@property (readonly) NSMutableSet *referencedProtocolNames;
@property (readonly) NSMutableSet *weaklyReferencedProtocolNames; // Protocols that can be forward-declared instead of imported
@property (nonatomic, readonly) NSArray *referencedClassNamesSortedByName;
@property (nonatomic, readonly) NSArray *referencedProtocolNamesSortedByName;
@property (nonatomic, readonly) NSArray *weaklyReferencedProtocolNamesSortedByName;
@property (nonatomic, readonly) NSString *referenceString;
@end
#pragma mark -
@implementation CDMultiFileVisitor
{
NSString *_outputPath;
NSDictionary *_frameworkNamesByClassName;
NSMutableSet *_referencedClassNames;
NSMutableSet *_referencedProtocolNames;
NSUInteger _referenceLocation;
}
- (id)init;
{
if ((self = [super init])) {
_referencedClassNames = [[NSMutableSet alloc] init];
_referencedProtocolNames = [[NSMutableSet alloc] init];
_weaklyReferencedProtocolNames = [[NSMutableSet alloc] init];
}
return self;
}
#pragma mark -
- (void)willBeginVisiting;
{
[super willBeginVisiting];
[self.classDump appendHeaderToString:self.resultString];
if (self.classDump.hasObjectiveCRuntimeInfo) {
[self buildClassFrameworks];
[self createOutputPathIfNecessary];
[self generateStructureHeader];
} else {
// TODO: (2007-06-14) Make sure this generates no output files in this case.
NSLog(@"Warning: This file does not contain any Objective-C runtime information.");
}
}
- (void)willVisitClass:(CDOCClass *)aClass;
{
// First, we set up some context...
[self.resultString setString:@""];
[self.classDump appendHeaderToString:self.resultString];
[self removeAllClassNameProtocolNameReferences];
NSString *str = [self importStringForClassName:aClass.superClassName];
if (str != nil) {
[self.resultString appendString:str];
[self.resultString appendString:@"\n"];
}
self.referenceLocation = [self.resultString length];
// And then generate the regular output
[super willVisitClass:aClass];
[self addReferencesToProtocolNamesInArray:aClass.protocolNames];
}
- (void)didVisitClass:(CDOCClass *)aClass;
{
// Generate the regular output
[super didVisitClass:aClass];
// Then insert the imports and write the file.
[self removeReferenceToClassName:aClass.name];
[self removeReferenceToClassName:aClass.superClassName];
NSString *referenceString = self.referenceString;
if (referenceString != nil)
[self.resultString insertString:referenceString atIndex:self.referenceLocation];
NSString *filename = [NSString stringWithFormat:@"%@.h", aClass.name];
if (self.outputPath != nil)
filename = [self.outputPath stringByAppendingPathComponent:filename];
[[self.resultString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filename atomically:YES];
}
- (void)willVisitCategory:(CDOCCategory *)category;
{
// First, we set up some context...
[self.resultString setString:@""];
[self.classDump appendHeaderToString:self.resultString];
[self removeAllClassNameProtocolNameReferences];
NSString *str = [self importStringForClassName:category.className];
if (str != nil) {
[self.resultString appendString:str];
[self.resultString appendString:@"\n"];
}
self.referenceLocation = [self.resultString length];
// And then generate the regular output
[super willVisitCategory:category];
[self addReferencesToProtocolNamesInArray:category.protocolNames];
}
- (void)didVisitCategory:(CDOCCategory *)category;
{
// Generate the regular output
[super didVisitCategory:category];
// Then insert the imports and write the file.
[self removeReferenceToClassName:category.className];
NSString *referenceString = self.referenceString;
if (referenceString != nil)
[self.resultString insertString:referenceString atIndex:self.referenceLocation];
NSString *filename = [NSString stringWithFormat:@"%@-%@.h", category.className, category.name];
if (self.outputPath != nil)
filename = [self.outputPath stringByAppendingPathComponent:filename];
[[self.resultString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filename atomically:YES];
}
- (void)willVisitProtocol:(CDOCProtocol *)protocol;
{
[self.resultString setString:@""];
[self.classDump appendHeaderToString:self.resultString];
[self removeAllClassNameProtocolNameReferences];
self.referenceLocation = [self.resultString length];
// And then generate the regular output
[super willVisitProtocol:protocol];
[self addReferencesToProtocolNamesInArray:protocol.protocolNames];
}
- (void)didVisitProtocol:(CDOCProtocol *)protocol;
{
// Generate the regular output
[super didVisitProtocol:protocol];
// Then insert the imports and write the file.
NSString *referenceString = self.referenceString;
if (referenceString != nil)
[self.resultString insertString:referenceString atIndex:self.referenceLocation];
NSString *filename = [NSString stringWithFormat:@"%@-Protocol.h", protocol.name];
if (self.outputPath != nil)
filename = [self.outputPath stringByAppendingPathComponent:filename];
[[self.resultString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filename atomically:YES];
}
#pragma mark - CDTypeControllerDelegate
- (void)typeController:(CDTypeController *)typeController didReferenceClassName:(NSString *)name;
{
[self addReferenceToClassName:name];
}
- (void)typeController:(CDTypeController *)typeController didReferenceProtocolNames:(NSArray *)names;
{
[self addWeakReferencesToProtocolNamesInArray:names];
}
#pragma mark -
- (NSString *)frameworkForClassName:(NSString *)name;
{
NSString *framework = self.frameworkNamesByClassName[name];
// Map public CoreFoundation classes to Foundation, because that is where the headers are exposed
if ([framework isEqualToString:@"CoreFoundation"] && [name hasPrefix:@"NS"]) {
framework = @"Foundation";
}
return framework;
}
- (NSString *)frameworkForProtocolName:(NSString *)name;
{
return self.frameworkNamesByProtocolName[name];
}
- (NSString *)importStringForClassName:(NSString *)name;
{
if (name != nil) {
NSString *framework = [self frameworkForClassName:name];
if (framework == nil)
return [NSString stringWithFormat:@"#import \"%@.h\"\n", name];
else
return [NSString stringWithFormat:@"#import <%@/%@.h>\n", framework, name];
}
return nil;
}
- (NSString *)importStringForProtocolName:(NSString *)name;
{
if (name != nil) {
NSString *framework = [self frameworkForProtocolName:name];
NSString *headerName = [name stringByAppendingString:@"-Protocol.h"];
if (framework == nil)
return [NSString stringWithFormat:@"#import \"%@\"\n", headerName];
else
return [NSString stringWithFormat:@"#import <%@/%@>\n", framework, headerName];
}
return nil;
}
#pragma mark - Class and Protocol name tracking
- (NSArray *)referencedClassNamesSortedByName;
{
return [[self.referencedClassNames allObjects] sortedArrayUsingSelector:@selector(compare:)];
}
- (NSArray *)referencedProtocolNamesSortedByName;
{
return [[self.referencedProtocolNames allObjects] sortedArrayUsingSelector:@selector(compare:)];
}
- (NSArray *)weaklyReferencedProtocolNamesSortedByName;
{
return [[self.weaklyReferencedProtocolNames allObjects] sortedArrayUsingSelector:@selector(compare:)];
}
- (void)addReferenceToClassName:(NSString *)className;
{
[self.referencedClassNames addObject:className];
}
- (void)removeReferenceToClassName:(NSString *)className;
{
if (className != nil)
[self.referencedClassNames removeObject:className];
}
- (void)addReferencesToProtocolNamesInArray:(NSArray *)protocolNames;
{
[self.referencedProtocolNames addObjectsFromArray:protocolNames];
}
- (void)addWeakReferencesToProtocolNamesInArray:(NSArray *)protocolNames;
{
[self.weaklyReferencedProtocolNames addObjectsFromArray:protocolNames];
}
- (void)removeAllClassNameProtocolNameReferences;
{
[self.referencedClassNames removeAllObjects];
[self.referencedProtocolNames removeAllObjects];
[self.weaklyReferencedProtocolNames removeAllObjects];
}
#pragma mark -
- (void)createOutputPathIfNecessary;
{
if (self.outputPath != nil) {
BOOL isDirectory;
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:self.outputPath isDirectory:&isDirectory] == NO) {
NSError *error = nil;
BOOL result = [fileManager createDirectoryAtPath:self.outputPath withIntermediateDirectories:YES attributes:nil error:&error];
if (result == NO) {
NSLog(@"Error: Couldn't create output directory: %@", self.outputPath);
NSLog(@"error: %@", error); // TODO: Test this
return;
}
} else if (isDirectory == NO) {
NSLog(@"Error: File exists at output path: %@", self.outputPath);
return;
}
}
}
#pragma mark -
// - imports for each referenced protocol
// - forward declarations for each referenced class
- (NSString *)referenceString;
{
NSMutableString *referenceString = [[NSMutableString alloc] init];
if ([self.referencedProtocolNames count] > 0) {
for (NSString *name in self.referencedProtocolNamesSortedByName) {
NSString *str = [self importStringForProtocolName:name];
if (str != nil)
[referenceString appendString:str];
}
[referenceString appendString:@"\n"];
}
BOOL addNewline = NO;
if ([self.referencedClassNames count] > 0) {
[referenceString appendFormat:@"@class %@;\n", [self.referencedClassNamesSortedByName componentsJoinedByString:@", "]];
addNewline = YES;
}
if ([self.weaklyReferencedProtocolNames count] > 0) {
[referenceString appendFormat:@"@protocol %@;\n", [self.weaklyReferencedProtocolNamesSortedByName componentsJoinedByString:@", "]];
addNewline = YES;
}
if (addNewline)
[referenceString appendString:@"\n"];
if ([referenceString length] == 0)
return nil;
return [referenceString copy];
}
#pragma mark -
- (void)buildClassFrameworks;
{
CDClassFrameworkVisitor *visitor = [[CDClassFrameworkVisitor alloc] init];
visitor.classDump = self.classDump;
[self.classDump recursivelyVisit:visitor];
self.frameworkNamesByClassName = visitor.frameworkNamesByClassName;
self.frameworkNamesByProtocolName = visitor.frameworkNamesByProtocolName;
}
- (void)generateStructureHeader;
{
[self.resultString setString:@""];
[self.classDump appendHeaderToString:self.resultString];
[self removeAllClassNameProtocolNameReferences];
self.referenceLocation = [self.resultString length];
[[self.classDump typeController] appendStructuresToString:self.resultString];
NSString *referenceString = [self referenceString];
if (referenceString != nil)
[self.resultString insertString:referenceString atIndex:self.referenceLocation];
NSString *filename = @"CDStructures.h";
if (self.outputPath != nil)
filename = [self.outputPath stringByAppendingPathComponent:filename];
[[self.resultString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filename atomically:YES];
}
@end