root/trunk/cocoa/mogenerator/mogenerator.m

Revision 343, 18.1 kB (checked in by wolf, 1 month ago)

[NEW] mogenerator 1.13.1: fix typo introduced in r314 where I was using -(will|Did)AccessValueForKey?: in relationship NSMutableSet getter when I should have been using -(will|Did)AChangeValueForKey:. (Pascal Augustin)

Line 
1 /*******************************************************************************
2         mogenerator.m
3                 Copyright (c) 2006-2008 Jonathan 'Wolf' Rentzsch: <http://rentzsch.com>
4                 Some rights reserved: <http://opensource.org/licenses/mit-license.php>
5
6         ***************************************************************************/
7
8 #import "mogenerator.h"
9
10 NSString        *gCustomBaseClass;
11
12 @implementation NSEntityDescription (customBaseClass)
13 - (BOOL)hasCustomSuperentity {
14         NSEntityDescription *superentity = [self superentity];
15         if (superentity) {
16                 return YES;
17         } else {
18                 return gCustomBaseClass ? YES : NO;
19         }
20 }
21 - (NSString*)customSuperentity {
22         NSEntityDescription *superentity = [self superentity];
23         if (superentity) {
24                 return [superentity managedObjectClassName];
25         } else {
26                 return gCustomBaseClass ? gCustomBaseClass : @"NSManagedObject";
27         }
28 }
29 /** @TypeInfo NSAttributeDescription */
30 - (NSArray*)noninheritedAttributes {
31         NSEntityDescription *superentity = [self superentity];
32         if (superentity) {
33                 NSMutableArray *result = [[[[self attributesByName] allValues] mutableCopy] autorelease];
34                 [result removeObjectsInArray:[[superentity attributesByName] allValues]];
35                 return result;
36         } else {
37                 return [[self attributesByName] allValues];
38         }
39 }
40 /** @TypeInfo NSAttributeDescription */
41 - (NSArray*)noninheritedRelationships {
42         NSEntityDescription *superentity = [self superentity];
43         if (superentity) {
44                 NSMutableArray *result = [[[[self relationshipsByName] allValues] mutableCopy] autorelease];
45                 [result removeObjectsInArray:[[superentity relationshipsByName] allValues]];
46                 return result;
47         } else {
48                 return [[self relationshipsByName] allValues];
49         }
50 }
51
52 #pragma mark Fetch Request support
53
54 - (NSDictionary*)fetchRequestTemplates {
55         // -[NSManagedObjectModel _fetchRequestTemplatesByName] is a private method, but it's the only way to get
56         //      model fetch request templates without knowing their name ahead of time. rdar://problem/4901396 asks for
57         //      a public method (-[NSManagedObjectModel fetchRequestTemplatesByName]) that does the same thing.
58         //      If that request is fulfilled, this code won't need to be modified thanks to KVC lookup order magic.
59     //  UPDATE: 10.5 now has a public -fetchRequestTemplatesByName method.
60         NSDictionary *fetchRequests = [[self managedObjectModel] valueForKey:@"fetchRequestTemplatesByName"];
61        
62         NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[fetchRequests count]];
63         nsenumerate ([fetchRequests allKeys], NSString, fetchRequestName) {
64                 NSFetchRequest *fetchRequest = [fetchRequests objectForKey:fetchRequestName];
65                 if ([fetchRequest entity] == self) {
66                         [result setObject:fetchRequest forKey:fetchRequestName];
67                 }
68         }
69         return result;
70 }
71 - (void)_processPredicate:(NSPredicate*)predicate_ bindings:(NSMutableArray*)bindings_ {
72     if (!predicate_) return;
73    
74         if ([predicate_ isKindOfClass:[NSCompoundPredicate class]]) {
75                 nsenumerate([(NSCompoundPredicate*)predicate_ subpredicates], NSPredicate, subpredicate) {
76                         [self _processPredicate:subpredicate bindings:bindings_];
77                 }
78         } else {
79                 assert([[(NSComparisonPredicate*)predicate_ leftExpression] expressionType] == NSKeyPathExpressionType);
80                 NSExpression *lhs = [(NSComparisonPredicate*)predicate_ leftExpression];
81                 NSExpression *rhs = [(NSComparisonPredicate*)predicate_ rightExpression];
82                 switch([rhs expressionType]) {
83                         case NSConstantValueExpressionType:
84                         case NSEvaluatedObjectExpressionType:
85                         case NSKeyPathExpressionType:
86                         case NSFunctionExpressionType:
87                                 //      Don't do anything with these.
88                                 break;
89                         case NSVariableExpressionType: {
90                                 // TODO SHOULD Handle LHS keypaths.
91                 
92                 NSString *type = nil;
93                
94                 NSAttributeDescription *attribute = [[self attributesByName] objectForKey:[lhs keyPath]];
95                 if (attribute) {
96                     type = [attribute objectAttributeType];
97                 } else {
98                     //  Probably a relationship
99                     NSRelationshipDescription *relationship = [[self relationshipsByName] objectForKey:[lhs keyPath]];
100                     assert(relationship);
101                     type = [[relationship destinationEntity] managedObjectClassName];
102                 }
103                 type = [type stringByAppendingString:@"*"];
104                
105                                 [bindings_ addObject:[NSDictionary dictionaryWithObjectsAndKeys:
106                                       [rhs variable], @"name",
107                                       type, @"type",
108                                       nil]];
109                         } break;
110                         default:
111                                 assert(0 && "unknown NSExpression type");
112                 }
113         }
114 }
115 - (NSArray*)prettyFetchRequests {
116         NSDictionary *fetchRequests = [self fetchRequestTemplates];
117         NSMutableArray *result = [NSMutableArray arrayWithCapacity:[fetchRequests count]];
118         nsenumerate ([fetchRequests allKeys], NSString, fetchRequestName) {
119                 NSFetchRequest *fetchRequest = [fetchRequests objectForKey:fetchRequestName];
120                 NSMutableArray *bindings = [NSMutableArray array];
121                 [self _processPredicate:[fetchRequest predicate] bindings:bindings];
122                 [result addObject:[NSDictionary dictionaryWithObjectsAndKeys:
123                            fetchRequestName, @"name",
124                            bindings, @"bindings",
125                            [NSNumber numberWithBool:[fetchRequestName hasPrefix:@"one"]], @"singleResult",
126                            nil]];
127         }
128         return result;
129 }
130 @end
131
132 @implementation NSAttributeDescription (scalarAttributeType)
133 - (BOOL)hasScalarAttributeType {
134         switch ([self attributeType]) {
135                 case NSInteger16AttributeType:
136                 case NSInteger32AttributeType:
137                 case NSInteger64AttributeType:
138                 case NSDoubleAttributeType:
139                 case NSFloatAttributeType:
140                 case NSBooleanAttributeType:
141                         return YES;
142                         break;
143                 default:
144                         return NO;
145         }
146 }
147 - (NSString*)scalarAttributeType {
148         switch ([self attributeType]) {
149                 case NSInteger16AttributeType:
150                         return @"short";
151                         break;
152                 case NSInteger32AttributeType:
153                         return @"int";
154                         break;
155                 case NSInteger64AttributeType:
156                         return @"long long";
157                         break;
158                 case NSDoubleAttributeType:
159                         return @"double";
160                         break;
161                 case NSFloatAttributeType:
162                         return @"float";
163                         break;
164                 case NSBooleanAttributeType:
165                         return @"BOOL";
166                         break;
167                 default:
168                         return nil;
169         }
170 }
171 - (BOOL)hasDefinedAttributeType {
172         return [self attributeType] != NSUndefinedAttributeType;
173 }
174 - (NSString*)objectAttributeType {
175 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1050
176     #define NSTransformableAttributeType 1800
177 #endif
178     if ([self attributeType] == NSTransformableAttributeType) {
179         NSString *result = [[self userInfo] objectForKey:@"attributeValueClassName"];
180         return result ? result : @"NSObject";
181     } else {
182         return [self attributeValueClassName];
183     }
184 }
185 @end
186
187 @implementation NSString (camelCaseString)
188 - (NSString*)camelCaseString {
189         NSArray *lowerCasedWordArray = [[self wordArray] arrayByMakingObjectsPerformSelector:@selector(lowercaseString)];
190         unsigned wordIndex = 1, wordCount = [lowerCasedWordArray count];
191         NSMutableArray *camelCasedWordArray = [NSMutableArray arrayWithCapacity:wordCount];
192         if (wordCount)
193                 [camelCasedWordArray addObject:[lowerCasedWordArray objectAtIndex:0]];
194         for (; wordIndex < wordCount; wordIndex++) {
195                 [camelCasedWordArray addObject:[[lowerCasedWordArray objectAtIndex:wordIndex] initialCapitalString]];
196         }
197         return [camelCasedWordArray componentsJoinedByString:@""];
198 }
199 @end
200
201 static MiscMergeEngine* engineWithTemplatePath(NSString *templatePath_) {
202         MiscMergeTemplate *template = [[[MiscMergeTemplate alloc] init] autorelease];
203         [template setStartDelimiter:@"<$" endDelimiter:@"$>"];
204         [template parseContentsOfFile:templatePath_];
205        
206         return [[[MiscMergeEngine alloc] initWithTemplate:template] autorelease];
207 }
208
209 @implementation MOGeneratorApp
210
211 NSString *ApplicationSupportSubdirectoryName = @"mogenerator";
212 - (NSString*)appSupportFileNamed:(NSString*)fileName_ {
213         NSFileManager *fileManager = [NSFileManager defaultManager];
214         BOOL isDirectory;
215        
216         if (templatePath) {
217                 if ([fileManager fileExistsAtPath:templatePath isDirectory:&isDirectory] && isDirectory) {
218                         return [templatePath stringByAppendingPathComponent:fileName_];
219                 }
220         } else {
221                 NSArray *appSupportDirectories = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask+NSLocalDomainMask, YES);
222                 assert(appSupportDirectories);
223                
224                 nsenumerate (appSupportDirectories, NSString*, appSupportDirectory) {
225                         if ([fileManager fileExistsAtPath:appSupportDirectory isDirectory:&isDirectory]) {
226                                 NSString *appSupportSubdirectory = [appSupportDirectory stringByAppendingPathComponent:ApplicationSupportSubdirectoryName];
227                                 if (templateGroup) {
228                                         appSupportSubdirectory = [appSupportSubdirectory stringByAppendingPathComponent:templateGroup];
229                                 }
230                                 if ([fileManager fileExistsAtPath:appSupportSubdirectory isDirectory:&isDirectory] && isDirectory) {
231                                         NSString *appSupportFile = [appSupportSubdirectory stringByAppendingPathComponent:fileName_];
232                                         if ([fileManager fileExistsAtPath:appSupportFile isDirectory:&isDirectory] && !isDirectory) {
233                                                 return appSupportFile;
234                                         }
235                                 }
236                         }
237                 }
238         }
239        
240         NSLog(@"appSupportFileNamed:@\"%@\": file not found", fileName_);
241         exit(EXIT_FAILURE);
242         return nil;
243 }
244
245 - (void) application: (DDCliApplication *) app
246     willParseOptions: (DDGetoptLongParser *) optionsParser;
247 {
248     [optionsParser setGetoptLongOnly: YES];
249     DDGetoptOption optionTable[] =
250     {
251     // Long             Short   Argument options
252     {@"model",          'm',    DDGetoptRequiredArgument},
253     {@"base-class",      0,     DDGetoptRequiredArgument},
254     // For compatibility:
255     {@"baseClass",      0,      DDGetoptRequiredArgument},
256     {@"includem",       0,      DDGetoptRequiredArgument},
257     {@"template-path",  0,      DDGetoptRequiredArgument},
258     // For compatibility:
259     {@"templatePath",   0,      DDGetoptRequiredArgument},
260     {@"output-dir",     'O',    DDGetoptRequiredArgument},
261     {@"machine-dir",    'M',    DDGetoptRequiredArgument},
262     {@"human-dir",      'H',    DDGetoptRequiredArgument},
263     {@"template-group", 0,      DDGetoptRequiredArgument},
264
265     {@"help",           'h',    DDGetoptNoArgument},
266     {@"version",        0,      DDGetoptNoArgument},
267     {nil,               0,      0},
268     };
269     [optionsParser addOptionsFromTable: optionTable];
270 }
271
272 - (void) printUsage;
273 {
274     ddprintf(@"%@: Usage [OPTIONS] <argument> [...]\n", DDCliApp);
275     printf("\n"
276            "  -m, --model MODEL             Path to model\n"
277            "      --base-class CLASS        Custom base class\n"
278            "      --includem FILE           Generate aggregate include file\n"
279            "      --template-path PATH      Path to templates\n"
280            "      --template-group NAME     Name of template group\n"
281            "  -O, --output-dir DIR          Output directory\n"
282            "  -M, --machine-dir DIR         Output directory for machine files\n"
283            "  -H, --human-dir DIR           Output director for human files\n"
284            "      --version                 Display version and exit\n"
285            "  -h, --help                    Display this help and exit\n"
286            "\n"
287            "Implements generation gap codegen pattern for Core Data.\n"
288            "Inspired by eogenerator.\n");
289 }
290
291 - (void) setModel: (NSString *) path;
292 {
293     assert(!model); // Currently we only can load one model.
294
295     if( ![[NSFileManager defaultManager] fileExistsAtPath:path]){
296         NSString * reason = [NSString stringWithFormat: @"error loading file at %@: no such file exists", path];
297         DDCliParseException * e = [DDCliParseException parseExceptionWithReason: reason
298                                                                        exitCode: EX_NOINPUT];
299         @throw e;
300     }
301
302     if ([[path pathExtension] isEqualToString:@"xcdatamodel"]) {
303         //      We've been handed a .xcdatamodel data model, transparently compile it into a .mom managed object model.
304         
305         //  Find where Xcode installed momc this week.
306         NSString *momc = nil;
307         if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Developer/usr/bin/momc"]) { // Xcode 3.1 installs it here.
308             momc = @"/Developer/usr/bin/momc";
309         } else if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Library/Application Support/Apple/Developer Tools/Plug-ins/XDCoreDataModel.xdplugin/Contents/Resources/momc"]) { // Xcode 3.0.
310             momc = @"/Library/Application Support/Apple/Developer Tools/Plug-ins/XDCoreDataModel.xdplugin/Contents/Resources/momc";
311         } else if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Developer/Library/Xcode/Plug-ins/XDCoreDataModel.xdplugin/Contents/Resources/momc"]) { // Xcode 2.4.
312             momc = @"/Developer/Library/Xcode/Plug-ins/XDCoreDataModel.xdplugin/Contents/Resources/momc";
313         }
314         assert(momc && "momc not found");
315        
316         tempMOMPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:[(id)CFUUIDCreateString(kCFAllocatorDefault, CFUUIDCreate(kCFAllocatorDefault)) autorelease]] stringByAppendingPathExtension:@"mom"];
317         system([[NSString stringWithFormat:@"\"%@\" \"%@\" \"%@\"", momc, path, tempMOMPath] UTF8String]); // Ignored system's result -- momc doesn't return any relevent error codes.
318         path = tempMOMPath;
319     }
320     model = [[[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path]] autorelease];
321     assert(model);
322 }
323
324 - (int) application: (DDCliApplication *) app
325    runWithArguments: (NSArray *) arguments;
326 {
327     if (_help)
328     {
329         [self printUsage];
330         return EXIT_SUCCESS;
331     }
332    
333     if (_version)
334     {
335         printf("mogenerator 1.13.1. By Jonathan 'Wolf' Rentzsch + friends.\n");
336         return EXIT_SUCCESS;
337     }
338    
339     gCustomBaseClass = [baseClass retain];
340     NSString * mfilePath = includem;
341         NSMutableString * mfileContent = [NSMutableString stringWithString:@""];
342     if (outputDir == nil)
343         outputDir = @"";
344     if (machineDir == nil)
345         machineDir = outputDir;
346     if (humanDir == nil)
347         humanDir = outputDir;
348
349         NSFileManager *fm = [NSFileManager defaultManager];
350    
351         int machineFilesGenerated = 0;       
352         int humanFilesGenerated = 0;
353        
354         if (model) {
355                 MiscMergeEngine *machineH = engineWithTemplatePath([self appSupportFileNamed:@"machine.h.motemplate"]);
356                 assert(machineH);
357                 MiscMergeEngine *machineM = engineWithTemplatePath([self appSupportFileNamed:@"machine.m.motemplate"]);
358                 assert(machineM);
359                 MiscMergeEngine *humanH = engineWithTemplatePath([self appSupportFileNamed:@"human.h.motemplate"]);
360                 assert(humanH);
361                 MiscMergeEngine *humanM = engineWithTemplatePath([self appSupportFileNamed:@"human.m.motemplate"]);
362                 assert(humanM);
363        
364                 int entityCount = [[model entities] count];
365        
366                 if(entityCount == 0){
367                         printf("No entities found in model. No files will be generated.\n");
368                         NSLog(@"the model description is %@.", model);
369                 }
370                
371                 nsenumerate ([model entities], NSEntityDescription, entity) {
372                         NSString *entityClassName = [entity managedObjectClassName];
373            
374                         if ([entityClassName isEqualToString:@"NSManagedObject"] ||
375                                 [entityClassName isEqualToString:gCustomBaseClass]){
376                                 ddprintf(@"skipping entity %@ because it doesn't use a custom subclass.\n",
377                          entityClassName);
378                                 continue;
379                         }
380                        
381                         NSString *generatedMachineH = [machineH executeWithObject:entity sender:nil];
382                         NSString *generatedMachineM = [machineM executeWithObject:entity sender:nil];
383                         NSString *generatedHumanH = [humanH executeWithObject:entity sender:nil];
384                         NSString *generatedHumanM = [humanM executeWithObject:entity sender:nil];
385                        
386                         BOOL machineDirtied = NO;
387                        
388                         NSString *machineHFileName = [machineDir stringByAppendingPathComponent:
389                 [NSString stringWithFormat:@"_%@.h", entityClassName]];
390                         if (![fm regularFileExistsAtPath:machineHFileName] || ![generatedMachineH isEqualToString:[NSString stringWithContentsOfFile:machineHFileName]]) {
391                                 //      If the file doesn't exist or is different than what we just generated, write it out.
392                                 [generatedMachineH writeToFile:machineHFileName atomically:NO];
393                                 machineDirtied = YES;
394                                 machineFilesGenerated++;
395                         }
396                         NSString *machineMFileName = [machineDir stringByAppendingPathComponent:
397                 [NSString stringWithFormat:@"_%@.m", entityClassName]];
398                         if (![fm regularFileExistsAtPath:machineMFileName] || ![generatedMachineM isEqualToString:[NSString stringWithContentsOfFile:machineMFileName]]) {
399                                 //      If the file doesn't exist or is different than what we just generated, write it out.
400                                 [generatedMachineM writeToFile:machineMFileName atomically:NO];
401                                 machineDirtied = YES;
402                                 machineFilesGenerated++;
403                         }
404                         NSString *humanHFileName = [humanDir stringByAppendingPathComponent:
405                 [NSString stringWithFormat:@"%@.h", entityClassName]];
406                         if ([fm regularFileExistsAtPath:humanHFileName]) {
407                                 if (machineDirtied)
408                                         [fm touchPath:humanHFileName];
409                         } else {
410                                 [generatedHumanH writeToFile:humanHFileName atomically:NO];
411                                 humanFilesGenerated++;
412                         }
413                         NSString *humanMFileName = [humanDir stringByAppendingPathComponent:
414                 [NSString stringWithFormat:@"%@.m", entityClassName]];
415                         NSString *humanMMFileName = [humanDir stringByAppendingPathComponent:
416                 [NSString stringWithFormat:@"%@.mm", entityClassName]];
417                         if (![fm regularFileExistsAtPath:humanMFileName] && [fm regularFileExistsAtPath:humanMMFileName]) {
418                                 //      Allow .mm human files as well as .m files.
419                                 humanMFileName = humanMMFileName;
420                         }
421                        
422                         if ([fm regularFileExistsAtPath:humanMFileName]) {
423                                 if (machineDirtied)
424                                         [fm touchPath:humanMFileName];
425                         } else {
426                                 [generatedHumanM writeToFile:humanMFileName atomically:NO];
427                                 humanFilesGenerated++;
428                         }
429                        
430                         [mfileContent appendFormat:@"#include \"%@\"\n#include \"%@\"\n",
431                 [humanMFileName lastPathComponent], [machineMFileName lastPathComponent]];
432                 }
433         }
434        
435         if (tempMOMPath) {
436                 [fm removeFileAtPath:tempMOMPath handler:nil];
437         }
438         bool mfileGenerated = NO;
439         if (mfilePath && ![mfileContent isEqualToString:@""]) {
440                 [mfileContent writeToFile:mfilePath atomically:NO];
441                 mfileGenerated = YES;
442         }
443        
444         printf("%d machine files%s %d human files%s generated.\n", machineFilesGenerated,
445                    (mfileGenerated ? "," : " and"), humanFilesGenerated, (mfileGenerated ? " and one include.m file" : ""));
446    
447     return EXIT_SUCCESS;
448 }
449
450 @end
451
452 int main (int argc, char * const * argv)
453 {
454     return DDCliAppRunWithClass([MOGeneratorApp class]);
455 }
Note: See TracBrowser for help on using the browser.