/ CoreData / NSManagedObject.m
NSManagedObject.m
  1  /* Copyright (c) 2008 Dan Knapp
  2  
  3  Permission is hereby granted, free of charge, to any person obtaining a copy of
  4  this software and associated documentation files (the "Software"), to deal in
  5  the Software without restriction, including without limitation the rights to
  6  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  7  the Software, and to permit persons to whom the Software is furnished to do so,
  8  subject to the following conditions:
  9  
 10  The above copyright notice and this permission notice shall be included in all
 11  copies or substantial portions of the Software.
 12  
 13  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 14  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 15  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 16  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 17  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 18  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
 19  #import "NSEntityDescription-Private.h"
 20  #import "NSManagedObjectContext-Private.h"
 21  #import "NSManagedObjectID-Private.h"
 22  #import "NSManagedObjectMutableSet.h"
 23  #import "NSManagedObjectSet.h"
 24  #import "NSManagedObjectSetEnumerator.h"
 25  #import <CoreData/NSAtomicStoreCacheNode.h>
 26  #import <CoreData/NSAttributeDescription.h>
 27  #import <CoreData/NSManagedObject.h>
 28  #import <CoreData/NSRelationshipDescription.h>
 29  #import <Foundation/NSRaise.h>
 30  
 31  NSString *const NSValidationKeyErrorKey = @"NSValidationErrorKey";
 32  NSString *const NSValidationObjectErrorKey = @"NSValidationErrorObject";
 33  NSString *const NSValidationPredicateErrorKey = @"NSValidationErrorPredicate";
 34  NSString *const NSValidationValueErrorKey = @"NSValidationErrorValue";
 35  
 36  @implementation NSManagedObject
 37  
 38  - init {
 39      NSLog(@"Error - can't initialize an NSManagedObject with -init");
 40      return nil;
 41  }
 42  
 43  - initWithObjectID: (NSManagedObjectID *) objectID
 44          managedObjectContext: (NSManagedObjectContext *) context
 45  {
 46      NSEntityDescription *entity = [objectID entity];
 47      NSString *className = [entity managedObjectClassName];
 48      Class class = NSClassFromString(className);
 49  
 50      if (class == Nil) {
 51          NSLog(@"Unable to find class %@ specified by entity %@ in the runtime, "
 52                @"using NSManagedObject,objectID=%@",
 53                className, [entity name], objectID);
 54  
 55          class = [NSManagedObject class];
 56      }
 57  
 58      [super dealloc];
 59      self = [class allocWithZone: NULL];
 60  
 61      _objectID = [objectID copy];
 62      _context = context;
 63      _committedValues = nil;
 64      _changedValues = [[NSMutableDictionary alloc] init];
 65      _isFault = YES;
 66      return self;
 67  }
 68  
 69  - initWithEntity: (NSEntityDescription *) entity
 70          insertIntoManagedObjectContext: (NSManagedObjectContext *) context
 71  {
 72      NSManagedObjectID *objectID =
 73              [[[NSManagedObjectID alloc] initWithEntity: entity] autorelease];
 74  
 75      if ((self = [self initWithObjectID: objectID
 76                    managedObjectContext: context]) == nil)
 77          return nil;
 78  
 79      NSDictionary *attributes = [entity attributesByName];
 80      NSEnumerator *state = [attributes keyEnumerator];
 81      NSString *key;
 82  
 83      while ((key = [state nextObject]) != nil) {
 84          id object = [attributes objectForKey: key];
 85          id value = [object defaultValue];
 86  
 87          if (value != nil)
 88              [self setPrimitiveValue: value forKey: key];
 89      }
 90  
 91      [context insertObject: self];
 92  
 93      return self;
 94  }
 95  
 96  - (void) dealloc {
 97      [self didTurnIntoFault];
 98  
 99      [_objectID release];
100      [_changedValues release];
101      [super dealloc];
102  }
103  
104  - (NSUInteger) hash {
105      return [_objectID hash];
106  }
107  
108  - (BOOL) isEqual: otherX {
109      if (![otherX isKindOfClass: [NSManagedObject class]])
110          return NO;
111  
112      NSManagedObject *other = otherX;
113  
114      return [_objectID isEqual: other->_objectID];
115  }
116  
117  - (NSEntityDescription *) entity {
118      return [_objectID entity];
119  }
120  
121  - (NSManagedObjectID *) objectID {
122      return _objectID;
123  }
124  
125  - self {
126      return self;
127  }
128  
129  - (NSManagedObjectContext *) managedObjectContext {
130      return _context;
131  }
132  
133  - (BOOL) isInserted {
134      return _isInserted;
135  }
136  
137  - (BOOL) isUpdated {
138      return _isUpdated;
139  }
140  
141  - (BOOL) isDeleted {
142      return _isDeleted;
143  }
144  
145  - (BOOL) isFault {
146      return _isFault;
147  }
148  
149  - (BOOL) hasFaultForRelationshipNamed: (NSString *) key {
150      NSUnimplementedMethod();
151      return NO;
152  }
153  
154  - (void) awakeFromFetch {
155      NSUnimplementedMethod();
156  }
157  
158  - (void) awakeFromInsert {
159      NSUnimplementedMethod();
160  }
161  
162  - (NSDictionary *) changedValues {
163      return _changedValues;
164  }
165  
166  - (NSDictionary *) _committedValues {
167  
168      if ([[self objectID] isTemporaryID])
169          return nil;
170  
171      if (_committedValues == nil) {
172          NSAtomicStoreCacheNode *node =
173                  [_context _cacheNodeForObjectID: [self objectID]];
174          NSDictionary *propertyCache = [node propertyCache];
175          NSMutableDictionary *storedValues = [[NSMutableDictionary alloc] init];
176  
177          NSArray *properties = [[self entity] properties];
178  
179          for (NSPropertyDescription *property in properties) {
180              NSString *name = [property name];
181  
182              if ([property isKindOfClass: [NSAttributeDescription class]]) {
183                  id value = [propertyCache objectForKey: name];
184  
185                  if (value != nil) {
186                      [storedValues setObject: value forKey: name];
187                  }
188              } else if ([property isKindOfClass: [NSRelationshipDescription
189                                                          class]]) {
190                  NSRelationshipDescription *relationship =
191                          (NSRelationshipDescription *) property;
192                  id indirectValue = [propertyCache objectForKey: name];
193  
194                  if (indirectValue != nil) {
195                      id value;
196  
197                      if (![relationship isToMany])
198                          value = [indirectValue objectID];
199                      else {
200                          value = [NSMutableSet set];
201  
202                          for (NSAtomicStoreCacheNode *relNode in indirectValue) {
203                              [value addObject: [relNode objectID]];
204                          }
205                      }
206  
207                      [storedValues setObject: value forKey: name];
208                  }
209              }
210          }
211  
212          [_committedValues release];
213          _committedValues = storedValues;
214      }
215      return _committedValues;
216  }
217  
218  - (NSDictionary *) committedValuesForKeys: (NSArray *) keys {
219      if (keys == nil)
220          return [self _committedValues];
221      else {
222          NSMutableDictionary *result = [NSMutableDictionary dictionary];
223  
224          for (NSString *key in keys) {
225              id object = [[self _committedValues] objectForKey: key];
226  
227              if (object != nil) {
228                  [result setObject: object forKey: key];
229              }
230          }
231  
232          return result;
233      }
234  }
235  
236  - (void) didSave {
237  }
238  
239  - (void) willTurnIntoFault {
240      // do nothing per doc.s
241  }
242  
243  - (void) didTurnIntoFault {
244      // do nothing also?
245  }
246  
247  - (void) willSave {
248  }
249  
250  - valueForKey: (NSString *) key {
251      if (!key)
252          return [self valueForUndefinedKey: nil];
253  
254      NSPropertyDescription *property =
255              [[self entity] _propertyForSelector: NSSelectorFromString(key)];
256      NSString *propertyName = [property name];
257  
258      if ([property isKindOfClass: [NSAttributeDescription class]]) {
259          [self willAccessValueForKey: propertyName];
260  
261          id result = [self primitiveValueForKey: propertyName];
262  
263          [self didAccessValueForKey: propertyName];
264  
265          return result;
266      } else if ([property isKindOfClass: [NSRelationshipDescription class]]) {
267          NSRelationshipDescription *relationship =
268                  (NSRelationshipDescription *) property;
269  
270          [self willAccessValueForKey: propertyName];
271  
272          id result = [self primitiveValueForKey: propertyName];
273  
274          if (result != nil) {
275              if ([relationship isToMany])
276                  result = [[[NSManagedObjectSet alloc]
277                          initWithManagedObjectContext: _context
278                                                   set: result] autorelease];
279              else
280                  result = [_context objectWithID: result];
281          }
282  
283          [self didAccessValueForKey: propertyName];
284  
285          return result;
286      }
287  
288      return [super valueForKey: key];
289  }
290  
291  - (void) setValue: value forKey: (NSString *) key {
292      NSPropertyDescription *property =
293              [[self entity] _propertyForSelector: NSSelectorFromString(key)];
294      NSString *propertyName = [property name];
295  
296      if ([property isKindOfClass: [NSAttributeDescription class]]) {
297          [self willChangeValueForKey: propertyName];
298          [self setPrimitiveValue: value forKey: propertyName];
299          [self didChangeValueForKey: propertyName];
300          return;
301      } else if ([property isKindOfClass: [NSRelationshipDescription class]]) {
302          NSRelationshipDescription *relationship =
303                  (NSRelationshipDescription *) property;
304          NSRelationshipDescription *inverse = [relationship inverseRelationship];
305          NSString *inverseName = [inverse name];
306          id valueByID;
307  
308          if ([relationship isToMany]) {
309              NSMutableSet *set = [NSMutableSet set];
310  
311              for (NSManagedObject *object in value)
312                  [set addObject: [object objectID]];
313  
314              valueByID = set;
315          } else {
316              valueByID = [value objectID];
317          }
318  
319          if (inverse != nil) {
320              id primitivePrevious = [self primitiveValueForKey: key];
321  
322              if (primitivePrevious != nil) {
323                  NSSet *allPrevious =
324                          [relationship isToMany]
325                                  ? primitivePrevious
326                                  : [NSSet setWithObject: primitivePrevious];
327  
328                  for (NSManagedObjectID *previousID in allPrevious) {
329                      NSManagedObject *previous =
330                              [_context objectWithID: previousID];
331  
332                      // FIXME: should be using set mutation notifications for to
333                      // many
334                      [previous willChangeValueForKey: inverseName];
335  
336                      if ([inverse isToMany])
337                          [[previous primitiveValueForKey: inverseName]
338                                  removeObject: [self objectID]];
339                      else
340                          [previous setPrimitiveValue: nil forKey: inverseName];
341  
342                      [previous didChangeValueForKey: inverseName];
343                  }
344              }
345          }
346  
347          [self willChangeValueForKey: propertyName];
348          [self setPrimitiveValue: valueByID forKey: propertyName];
349          [self didChangeValueForKey: propertyName];
350  
351          if (inverse != nil) {
352              NSSet *allValues = [relationship isToMany]
353                                         ? valueByID
354                                         : [NSSet setWithObject: valueByID];
355  
356              for (NSManagedObjectID *valueID in allValues) {
357                  NSManagedObject *value = [_context objectWithID: valueID];
358  
359                  // FIXME: should be using set mutation notifications for to many
360                  [value willChangeValueForKey: inverseName];
361  
362                  if ([inverse isToMany]) {
363                      NSMutableSet *set =
364                              [value primitiveValueForKey: inverseName];
365  
366                      if (set == nil) {
367                          set = [NSMutableSet set];
368                          [value setPrimitiveValue: set forKey: inverseName];
369                      }
370  
371                      [set addObject: [self objectID]];
372                  } else {
373                      [value setPrimitiveValue: [self objectID]
374                                        forKey: inverseName];
375                  }
376  
377                  [value didChangeValueForKey: inverseName];
378              }
379          }
380          return;
381      }
382  
383      [super setValue: value forKey: key];
384  }
385  
386  - (NSMutableSet *) mutableSetValueForKey: (NSString *) key {
387      return [[[NSManagedObjectMutableSet alloc] initWithManagedObject: self
388                                                                   key: key]
389              autorelease];
390  }
391  
392  - primitiveValueForKey: (NSString *) key {
393      id result = [_changedValues objectForKey: key];
394  
395      if (result == nil)
396          result = [[self _committedValues] objectForKey: key];
397  
398      return result;
399  }
400  
401  - (void) setPrimitiveValue: value forKey: (NSString *) key {
402      if (value == nil)
403          [_changedValues removeObjectForKey: key];
404      else {
405          [_changedValues setObject: value forKey: key];
406      }
407  }
408  
409  - (BOOL) validateValue: (id *) value
410                  forKey: (NSString *) key
411                   error: (NSError **) error
412  {
413      NSUnimplementedMethod();
414      return YES;
415  }
416  
417  - (BOOL) validateForDelete: (NSError **) error {
418      NSUnimplementedMethod();
419      return YES;
420  }
421  
422  - (BOOL) validateForInsert: (NSError **) error {
423      NSUnimplementedMethod();
424      return YES;
425  }
426  
427  - (BOOL) validateForUpdate: (NSError **) error {
428      NSUnimplementedMethod();
429      return YES;
430  }
431  
432  + (BOOL) automaticallyNotifiesObserversForKey: (NSString *) key {
433      // FIXME: Doc.s for NSManagedObject state this returns YES for unmodeled
434      // properties.
435  
436      return NO;
437  }
438  
439  - (void) didAccessValueForKey: (NSString *) key {
440  }
441  
442  - (void *) observationInfo {
443      return _observationInfo;
444  }
445  
446  - (void) setObservationInfo: (void *) value {
447      _observationInfo = value;
448  }
449  
450  - (void) willAccessValueForKey: (NSString *) key {
451  }
452  
453  - (NSString *) description {
454      NSMutableDictionary *values = [NSMutableDictionary
455              dictionaryWithDictionary: [self _committedValues]];
456  
457      [values addEntriesFromDictionary: _changedValues];
458  
459      return [NSString
460              stringWithFormat: @"<%@ %x:objectID=%@ entity name=%@, values=%@>",
461                                [self class], self, _objectID, [self entity],
462                                values];
463  }
464  
465  @end