GNUstep Core Data  0.1
NSManagedObjectModel.m
00001 /* Implementation of the NSManagedObjectModel class for the GNUstep
00002    Core Data framework.
00003    Copyright (C) 2005 Free Software Foundation, Inc.
00004 
00005    Written by:  Saso Kiselkov <diablos@manga.sk>
00006    Date: August 2005
00007 
00008    This file is part of the GNUstep Core Data framework.
00009 
00010    This library is free software; you can redistribute it and/or
00011    modify it under the terms of the GNU Lesser General Public
00012    License as published by the Free Software Foundation; either
00013    version 2.1 of the License, or (at your option) any later version.
00014 
00015    This library is distributed in the hope that it will be useful,
00016    but WITHOUT ANY WARRANTY; without even the implied warranty of
00017    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00018    Lesser General Public License for more details.
00019 
00020    You should have received a copy of the GNU Lesser General Public
00021    License along with this library; if not, write to the Free
00022    Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA.
00023  */
00024 
00025 #import "CoreDataHeaders.h"
00026 
00033 static void EnsureEntitiesHaveProperNames(NSArray * entities)
00034 {
00035   NSMutableSet * knownNames;
00036   NSEnumerator * e;
00037   NSEntityDescription * entity;
00038 
00039   knownNames = [NSMutableSet setWithCapacity: [entities count]];
00040   e = [entities objectEnumerator];
00041   while ((entity = [e nextObject]) != nil)
00042     {
00043       NSString * entityName = [entity name];
00044 
00045       if (entityName == nil)
00046         {
00047           [NSException raise: NSInvalidArgumentException
00048                       format: _(@"Tried to add an entity without a name "
00049             @"to a managed object model.")];
00050         }
00051       if ([knownNames containsObject: entityName])
00052         {
00053           [NSException raise: NSInvalidArgumentException
00054                       format: _(@"Tried to add several entities with the "
00055             @"same name to a managed object model.")];
00056         }
00057       [knownNames addObject: entityName];
00058     }
00059 }
00060 
00061 @interface NSManagedObjectModel (GSCoreDataInternal)
00062 
00067 - (void) _grabEntities: (NSArray *) entities;
00068 
00070 - (void) _ungrabEntities: (NSArray *) entities;
00071 
00077 - (void) _ensureEditableWithReason: (NSString *) reason;
00078 
00079 @end
00080 
00081 @implementation NSManagedObjectModel (GSCoreDataInternal)
00082 
00083 - (void) _grabEntities: (NSArray *) entities
00084 {
00085   NSEnumerator * e;
00086   NSEntityDescription * ent;
00087 
00088   e = [entities objectEnumerator];
00089   while ((ent = [e nextObject]) != nil)
00090     {
00091       if ([ent managedObjectModel] != nil && [ent managedObjectModel] != self)
00092         {
00093           [NSException raise: NSInvalidArgumentException
00094                       format: _(@"Passed an entity to an object model already "
00095                                 @"in use by some other model")];
00096         }
00097       [ent _addReferenceToManagedObjectModel: self];
00098     }
00099 }
00100 
00101 - (void) _ungrabEntities: (NSArray *) entities
00102 {
00103   NSEnumerator * e;
00104   NSEntityDescription * ent;
00105 
00106   e = [entities objectEnumerator];
00107   while ((ent = [e nextObject]) != nil)
00108     {
00109       [ent _removeReferenceToManagedObjectModel: self];
00110     }
00111 }
00112 
00113 - (void) _ensureEditableWithReason: (NSString *) reason
00114 {
00115   if (_usedByPersistentStoreCoordinators)
00116     {
00117       // which exception to raise??
00118       [NSException raise: NSGenericException format: _(reason)];
00119     }
00120 }
00121 
00122 @end
00123 
00124 @implementation NSManagedObjectModel
00125 
00126 - (void) dealloc
00127 {
00128   NSEnumerator * e;
00129   NSArray * entities;
00130 
00131   // ungrab the entities before we disappear
00132   [self _ungrabEntities: _entities];
00133   TEST_RELEASE(_entities);
00134 
00135   e = [_configurations objectEnumerator];
00136   while ((entities = [e nextObject]) != nil)
00137     {
00138       [self _ungrabEntities: entities];
00139     }
00140   TEST_RELEASE(_configurations);
00141 
00142   TEST_RELEASE(_fetchRequests);
00143 
00144   [super dealloc];
00145 }
00146 
00147 + (NSManagedObjectModel *) modelByMergingModels: (NSArray *) models
00148 {
00149   NSManagedObjectModel * newModel;
00150 
00151   NSMutableArray * entities;
00152   NSMutableDictionary * confs;
00153   NSMutableDictionary * fetchRequests;
00154 
00155   NSEnumerator * e;
00156   NSManagedObjectModel * model;
00157 
00158   NSString * confName;
00159   NSString * fetchRequestName;
00160 
00161   newModel = [[NSManagedObjectModel new] autorelease];
00162 
00163   entities = [NSMutableArray array];
00164   confs = [NSMutableDictionary dictionary];
00165   fetchRequests = [NSMutableDictionary dictionary];
00166 
00167   // copy and merge all the contents from all models
00168   e = [models objectEnumerator];
00169   while ((model = [e nextObject]) != nil)
00170     {
00171       [entities addObjectsFromArray: [[[NSArray alloc]
00172         initWithArray: [model entities] copyItems: YES] autorelease]];
00173 
00174       [confs addEntriesFromDictionary: [[[NSDictionary alloc]
00175         initWithDictionary: [model _configurationsByName] copyItems: YES]
00176         autorelease]];
00177 
00178       // fetch requests can be shared
00179       [fetchRequests addEntriesFromDictionary:  [model fetchRequestsByName]];
00180     }
00181 
00182   // and set the merged contents into the new model
00183   [newModel setEntities: entities];
00184 
00185   e = [[confs allKeys] objectEnumerator];
00186   while ((confName = [e nextObject]) != nil)
00187     {
00188       [newModel setEntities: [confs objectForKey: confName]
00189            forConfiguration: confName];
00190     }
00191 
00192   e = [[fetchRequests allKeys] objectEnumerator];
00193   while ((fetchRequestName = [e nextObject]) != nil)
00194     {
00195       [newModel setFetchRequestTemplate: [fetchRequests objectForKey:
00196         fetchRequestName]
00197                                 forName: fetchRequestName];
00198     }
00199 
00200   return newModel;
00201 }
00202 
00203 + (NSManagedObjectModel *) mergedModelFromBundles: (NSArray *) bundles
00204 {
00205   NSArray * modelPaths;
00206   NSMutableArray * models;
00207 
00208   NSEnumerator * e;
00209   NSString * modelPath;
00210 
00211   // find the involved .gsdatamodel files
00212   if (bundles != nil)
00213    // search specified bundles
00214     {
00215       NSEnumerator * e;
00216       NSBundle * bundle;
00217       NSMutableArray * array;
00218 
00219       array = [NSMutableArray array];
00220 
00221       e = [bundles objectEnumerator];
00222       while ((bundle = [e nextObject]) != nil)
00223         {
00224           [array addObjectsFromArray:
00225             [bundle pathsForResourcesOfType: @"gsdatamodel" inDirectory: nil]];
00226         }
00227 
00228       modelPaths = array;
00229     }
00230   else
00231    // search the main bundle
00232     {
00233       modelPaths = [[NSBundle mainBundle]
00234         pathsForResourcesOfType: @"gsdatamodel" inDirectory: nil];
00235     }
00236 
00237 
00238   // initialize the models from them
00239   models = [NSMutableArray arrayWithCapacity: [modelPaths count]];
00240   e = [modelPaths objectEnumerator];
00241   while ((modelPath = [e nextObject]) != nil)
00242     {
00243       [models addObject: [[[NSManagedObjectModel alloc]
00244         initWithContentsOfFile: modelPath]
00245         autorelease]];
00246     }
00247 
00248   // and return the merged result
00249   return [self modelByMergingModels: models];
00250 }
00251 
00252 - (id) initWithContentsOfURL: (NSURL *) url
00253 {
00254   NSData * data;
00255 
00256   // release the old instance - we'll return a new one
00257   [self release];
00258 
00259   if ((data = [NSData dataWithContentsOfURL: url]) == nil)
00260     {
00261       NSLog(_(@"Failed to access managed object model archive at: %@"),
00262         [url description]);
00263 
00264       return nil;
00265     }
00266 
00267   return [NSKeyedUnarchiver unarchiveObjectWithData: data];
00268 }
00269 
00270 - (id) _initWithContentsOfFile: (NSString *) file
00271 {
00272   return [self initWithContentsOfURL: [NSURL fileURLWithPath: file]];
00273 }
00274 
00275 - (id) init
00276 {
00277         if ((self = [super init]))
00278     {
00279       _configurations = [NSMutableDictionary new];
00280       _fetchRequests = [NSMutableDictionary new];
00281         }
00282       return self;
00283 }
00284 
00285 - (NSArray *) entities
00286 {
00287   return _entities;
00288 }
00289 
00290 - (NSDictionary *) entitiesByName
00291 {
00292   NSMutableDictionary * dict = [NSMutableDictionary
00293     dictionaryWithCapacity: [_entities count]];
00294   NSEnumerator * e = [_entities objectEnumerator];
00295   NSEntityDescription * entity;
00296 
00297   while ((entity = [e nextObject]) != nil)
00298     {
00299       [dict setObject: entity forKey: [entity name]];
00300     }
00301 
00302   return [[dict copy] autorelease];
00303 }
00304 
00305 - (void) setEntities: (NSArray *) someEntities
00306 {
00307   [self _ensureEditableWithReason: @"Tried to set entities of a "
00308     @"managed object model already in use by an object graph manager."];
00309   EnsureEntitiesHaveProperNames(someEntities);
00310 
00311   if (_entities != nil)
00312     {
00313       [self _ungrabEntities: _entities];
00314       DESTROY(_entities);
00315     }
00316   if (someEntities != nil)
00317     {
00318       _entities = [someEntities copy];
00319       [self _grabEntities: _entities];
00320     }
00321 }
00322 
00323 - (NSArray *) configurations
00324 {
00325   return [_configurations allKeys];
00326 }
00327 
00328 - (NSArray *) entitiesForConfiguration: (NSString *) conf
00329 {
00330   return [_configurations objectForKey: conf];
00331 }
00332 
00333 - (void) setEntities: (NSArray *) entities
00334     forConfiguration: (NSString *) conf
00335 {
00336   NSArray * oldEntities;
00337 
00338   [self _ensureEditableWithReason: @"Tried to set entities "
00339     @"for a configuration of a managed object model already in use "
00340     @"by an object graph manager."];
00341   EnsureEntitiesHaveProperNames(entities);
00342 
00343   oldEntities = [_configurations objectForKey: conf];
00344   if (oldEntities != nil)
00345     {
00346       [self _ungrabEntities: oldEntities];
00347       [_configurations removeObjectForKey: conf];
00348     }
00349   if (entities != nil)
00350     {
00351       [_configurations setObject: [[entities copy] autorelease]
00352                           forKey: conf];
00353       [self _grabEntities: entities];
00354     }
00355 }
00356 
00357 - (NSDictionary *) _configurationsByName
00358 {
00359   return [[_configurations copy] autorelease];
00360 }
00361 
00362 - (NSFetchRequest *) fetchRequestTemplateForName: (NSString *) aName
00363 {
00364   return [_fetchRequests objectForKey: aName];
00365 }
00366 
00367 - (NSFetchRequest *) fetchRequestFromTemplateWithName: (NSString *) name
00368                                 substitutionVariables: (NSDictionary *) vars
00369 {
00370   NSFetchRequest * req, * template;
00371 
00372   template = [_fetchRequests objectForKey: name];
00373   if (template == nil)
00374     {
00375       return nil;
00376     }
00377 
00378   req = [[template copy] autorelease];
00379   if ([req predicate] != nil)
00380     {
00381       [req setPredicate: [[req predicate]
00382         predicateWithSubstitutionVariables: vars]];
00383     }
00384 
00385   return req;
00386 }
00387 
00388 - (void) setFetchRequestTemplate: (NSFetchRequest *) request
00389                          forName: (NSString *) name
00390 {
00391   if (_usedByPersistentStoreCoordinators)
00392     {
00393       [NSException raise: NSGenericException
00394                   format: _(@"Tried to set a fetch request template "
00395                              @"for a managed object model already in use "
00396                              @"by an object graph manager.")];
00397     }
00398 
00399   // N.B. is this the way it should behave?
00400   if (request != nil)
00401     {
00402       [_fetchRequests setObject: request forKey: name];
00403     }
00404   else
00405     {
00406       [_fetchRequests removeObjectForKey: name];
00407     }
00408 }
00409 
00410 - (void) _removeFetchRequestTemplateForName: (NSString *) name
00411 {
00412   if (_usedByPersistentStoreCoordinators)
00413     {
00414       [NSException raise: NSGenericException
00415                   format: _(@"Tried to remove a fetch request template "
00416                             @"from a managed object model already in use "
00417                             @"by an object graph manager.")];
00418     }
00419 
00420   [_fetchRequests removeObjectForKey: name];
00421 }
00422 
00423 - (NSDictionary *) fetchRequestsByName
00424 {
00425   return [[_fetchRequests copy] autorelease];
00426 }
00427 
00428 - (NSDictionary *) localizationDictionary
00429 {
00430   // FIXME: what is this supposed to do ???
00431 
00432   return nil;
00433 }
00434 
00435 - (void) setLocalizationDictionary: (NSDictionary *) dict
00436 {
00437   // FIXME: what is this supposed to do ???
00438 }
00439 
00440 - (BOOL) _isEditable
00441 {
00442   return (_usedByPersistentStoreCoordinators == 0);
00443 }
00444 
00445 // NSCoding
00446 
00447 - (id) initWithCoder: (NSCoder *) coder
00448 {
00449         if ((self = [super init]))
00450     {
00451       if ([coder allowsKeyedCoding])
00452         {
00453           ASSIGN(_entities, [coder decodeObjectForKey: @"Entities"]);
00454           ASSIGN(_configurations, [coder decodeObjectForKey:
00455             @"Configurations"]);
00456           ASSIGN(_fetchRequests, [coder decodeObjectForKey: @"FetchRequests"]);
00457         }
00458       else
00459         {
00460           ASSIGN(_entities, [coder decodeObject]);
00461           ASSIGN(_configurations, [coder decodeObject]);
00462           ASSIGN(_fetchRequests, [coder decodeObject]);
00463         }
00464         }
00465       return self;
00466 }
00467 
00468 - (void) encodeWithCoder: (NSCoder *) coder
00469 {
00470   if ([coder allowsKeyedCoding])
00471     {
00472       [coder encodeObject: _entities forKey: @"Entities"];
00473       [coder encodeObject: _configurations forKey: @"Configurations"];
00474       [coder encodeObject: _fetchRequests forKey: @"FetchRequests"];
00475     }
00476   else
00477     {
00478       [coder encodeObject: _entities];
00479       [coder encodeObject: _configurations];
00480       [coder encodeObject: _fetchRequests];
00481     }
00482 }
00483 
00484 // NSCopying
00485 
00486 - (id) copyWithZone: (NSZone *) zone
00487 {
00488   NSManagedObjectModel * model;
00489 
00490   NSEnumerator * e;
00491   NSString * conf;
00492   NSString * fetchRequestName;
00493 
00494   model = [[NSManagedObjectModel allocWithZone: zone] init];
00495 
00496    // We must copy entities and configurations themselves too - they are
00497    // not shareable between several models. (FIXME: is this true? Apple
00498    // spec doesn't say a word about this - I just *guessed* it)
00499   [model setEntities: [[[NSArray alloc]
00500     initWithArray: _entities copyItems: YES]
00501     autorelease]];
00502 
00503   e = [[_configurations allKeys] objectEnumerator];
00504   while ((conf = [e nextObject]) != nil)
00505     {
00506       [model setEntities: [[[NSArray alloc]
00507         initWithArray: [_configurations objectForKey: conf]
00508             copyItems: YES]
00509         autorelease]
00510         forConfiguration: conf];
00511     }
00512 
00513    // fetch requests appear to be shareable, so just set them
00514   e = [[_fetchRequests allKeys] objectEnumerator];
00515   while ((fetchRequestName = [e nextObject]) != nil)
00516     {
00517       [model setFetchRequestTemplate: [_fetchRequests objectForKey:
00518         fetchRequestName]
00519                              forName: fetchRequestName];
00520     }
00521 
00522   return model;
00523 }
00524 
00529 - (void) _incrementUseCount
00530 {
00531   _usedByPersistentStoreCoordinators++;
00532 }
00533 
00539 - (void) _decrementUseCount
00540 {
00541   NSAssert(_usedByPersistentStoreCoordinators > 0,
00542     _(@"Tried to underflow managed object model use count."));
00543 
00544   _usedByPersistentStoreCoordinators--;
00545 }
00546 
00547 @end