// =========================================================================== // NSManagedObject+Extra.m (c) 2006 __MyCompanyName__. All rights reserved. // // --------------------------------------------------------------------------- // Modification History: // // Created: 1/17/06 Pierce T. Wetter III // Last Modified: 1/17/06 Pierce T. Wetter III // // --------------------------------------------------------------------------- // // =========================================================================== #import "NSManagedObject+Extra.h" @implementation NSManagedObject (Extra) - (NSString *) appleScriptName { return [[[self entity] name] lowercaseString]; } + appleScriptToManyKeys { return [NSArray array]; } - (NSArray *)indicesOfObjectsByEvaluatingObjectSpecifier:(NSScriptObjectSpecifier *)specifier { // Per the NSScriptObjectSpecifiers informal protocol. Returns an array of NSNumber objects representing indexes identifying objects contained by the document (in other words, AppleScript company or person elements of the document) that match specifier. If all objects match, the returned array contains a single NSNumber object representing -1; if no objects match, the returned array is empty. Returning nil causes the object specifier to do its own evaluation. Every scriptable class that contains other objects may implement -indicesOfObjectsByEvaluatingObjectSpecifier: to accelerate the evaluation of objects they contain. This is useful for range and relative specifiers that specify multiple objects in a large database or data stores that emulate indexed access, because otherwise Cocoa falls back on much slower techniques of its own. Index specifiers, unique id specifiers and name specifiers for individual objects call the appropriate NSScriptKeyValueCoding methods instead of this method, because they are implemented below. // In evaluating NSIndexSpecifier, a -key value returning an empty string indicates that the index specifier specifies an indexed member of an array of objects resulting from evaluation of the container specifier, not an indexed element of some particular class of elements within the container. In that case, -keyClassDescription is the container specifier's key class description. This usage allows an application to have an umbrella class like 'graphics' with subclasses like 'rectangle' and 'circle' that are treated as 'graphics' objects and indexed together, as illustrated in Apple's Sketch-112 sample code. See the Mac OS X 10.2 Release Notes for Cocoa Scripting. Wareroom Demo does not utilitize this capability because companies and persons cannot be listed together as members of a single umbrella class. For example, we treat mixed kinds of managed objects as an error in a list parameter to the 'duplicate' command in -WRCloneCommand. NSArray *wantedKeys = [[self class] appleScriptToManyKeys]; // list every key that represents an object contained in a Wareroom Demo document and that Wareroom Demo can evaluate NSString *key = [specifier key]; if ([wantedKeys containsObject:key]) { if ([specifier isKindOfClass:[NSRangeSpecifier class]]) { return [self indicesOfObjectsByEvaluatingRangeSpecifier:(NSRangeSpecifier *)specifier]; } else if ([specifier isKindOfClass:[NSRelativeSpecifier class]]) { return [self indicesOfObjectsByEvaluatingRelativeSpecifier:(NSRelativeSpecifier *)specifier]; } } return nil; // let specifier evaluate itself } - (NSArray *)indicesOfObjectsByEvaluatingRangeSpecifier:(NSRangeSpecifier *)specifier { // An array of linked persons may represent discontiguous persons in a Wareroom Demo document, but our implementation of -linkedPersons ensures that they are always ordered by their orderedIndex attributes. (The orderedIndex attribute is the order of the person within the document; this number is not the same as the index of the linked person within the person, but they are in the same order.) This method evaluates the start and end specifiers to obtain the start and end persons, then it obtains the start and end persons' Cocoa zero-based indexes within the linked persons array. Key was tested in -indicesOfObjectsByEvaluatingObjectSpecifier. NSString *key = [specifier key]; // "linkedPersons" int count = [[self valueForKeyPath:[NSString stringWithFormat:@"%@.@%@", key, NSCountKeyValueOperator]] intValue]; if (count == 0) return [NSArray array]; // no match possible NSScriptObjectSpecifier *startSpecifier = [specifier startSpecifier]; NSScriptObjectSpecifier *endSpecifier = [specifier endSpecifier]; if ((startSpecifier == nil) && (endSpecifier == nil)) return nil; // error; bad range specifier // Get the start person. int startIndex; NSManagedObject *startObject=nil; if (startSpecifier != nil) { startObject = [startSpecifier objectsByEvaluatingWithContainers:self]; } // Get the end person. int endIndex; NSManagedObject *endObject=nil; if (endSpecifier != nil) { endObject = [endSpecifier objectsByEvaluatingWithContainers:self]; } if (startObject == nil) { // Force evaluation error in order to get error message from WRRangeSpecifier category when the start object does not exist. [startSpecifier setEvaluationErrorNumber:NSInternalSpecifierError]; return nil; // error; end person not in array } if (endObject == nil) { // Force evaluation error in order to get error message from WRRangeSpecifier category when the end object does not exist. [endSpecifier setEvaluationErrorNumber:NSInternalSpecifierError]; return nil; // error; end person not in array } // Return the range's indexes within linked persons, in an array. NSMutableArray *returnArray = [NSMutableArray array]; // no objects match NSArray *list=[self valueForKey:key]; startIndex = [list indexOfObjectIdenticalTo:startObject]; endIndex = [list indexOfObjectIdenticalTo:endObject]; // Always return indexes in ascending order of orderedIndex even if the range is specified backwards. if (endIndex < startIndex) { int t=endIndex; endIndex=startIndex; startIndex=t; } if ((startIndex != NSNotFound) && (endIndex != NSNotFound)) { int index; for (index = startIndex; index <= endIndex; index++) { [returnArray addObject:[NSNumber numberWithInt:index]]; // Cocoa zero-based index within linkedPersons } } return [[returnArray copy] autorelease]; } - (NSArray *)indicesOfObjectsByEvaluatingRelativeSpecifier:(NSRelativeSpecifier *)specifier { // Since a relative specifier's base specifier is not necessarily an index specifier, this method must access the database to obtain the base index. It does this using a fast Core Data accessor to get the value of the orderedIndex attribute of the company or person. Using Core Data key-value coding makes it possible to avoid testing key to call the specific company or person -index methods. Wareroom Demo indexes are one-based, so they must be converted to zero-based indexes upon return. Key was tested in -indicesOfObjectsByEvaluatingObjectSpecifier. NSString *key = [specifier key]; // "companies" or "persons" int count = [[self valueForKeyPath:[NSString stringWithFormat:@"%@.@%@", key, NSCountKeyValueOperator]] intValue]; if (count == 0) return [NSArray array]; // no match possible NSScriptObjectSpecifier *baseSpecifier = [specifier baseSpecifier]; if (baseSpecifier == nil) return nil; // error; bad relative specifier // Get the base index. NSManagedObject *evaluatedBaseSpecifier = [baseSpecifier objectsByEvaluatingWithContainers:self]; if (evaluatedBaseSpecifier == nil) { // Force evaluation error in order to get error message from WRRelativeSpecifier category for ' before ' and ' after ', which would otherwise return a correct result even though the base object does not exist. [baseSpecifier setEvaluationErrorNumber:NSInternalSpecifierError]; return nil; // error; base object not in array } int baseIndex = [[evaluatedBaseSpecifier valueForKey:@"uniqueID"] intValue]; // Build the return array. int returnIndex = ([specifier relativePosition] == NSPositionBefore) ? --baseIndex : ++baseIndex; if ((returnIndex < 1) || (returnIndex > count)) return nil; return [NSArray arrayWithObject:[NSNumber numberWithInt:returnIndex - 1]]; // convert to Cocoa zero-based index } - (id)valueAtIndex:(unsigned)index inPropertyWithKey: (NSString *) key { // Per NSScriptKeyValueCoding informal protocol, for -valueAtIndex:inPropertyWithKey:. // AppleScript: 'get company of '. // Execute a fetch request and return the company at index index. NSArray *result=[self valueForKey:key]; return (index < [result count] ? [result objectAtIndex: index] : nil); } - (NSArray *)toManyInApplescriptOrder: (NSString *) relationship { // AppleScript: 'get persons of ' or 'get every person of ', returning list of person (read-only). // An AppleScript list must be an array, so convert the set returned by -persons to an array. The order of items in the array is undefined unless we do something about it. To enable meaningful and efficient handling of range and relative specifiers and to ensure consistency over time, we therefore sort the returned array by its elements' orderedIndex attributes, so that linked persons always have the same emulated ordering as the document's persons array but with discontiguous membership. (This implies that insertion of linked persons using index or 'at end' and the like is meaningless, while accessing them by index is meaningful.) // This is implemented as an accessor method, even though it does not access an instance variable, so that it can be defined in the sdef file as the key attribute of the cocoa element defining the persons element of the company class. The AppleScript element will thus be treated as an ordered array of linked persons. If it weren't for the desire to have named 'linked persons' elements in the sdef file, we could have implemented the key-value coding methods -countOf and -objectInAtIndex:, instead. // Invoke [self persons] to get linked persons as a set, then convert the set to a mutable array. NSMutableArray *tempArray = [[[self valueForKey:relationship] allObjects] mutableCopy]; // Sort the array by orderedIndex. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"uniqueID" ascending:YES]; [tempArray sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; [sortDescriptor release]; // Return an autoreleased sorted immutable array. NSArray *returnArray = [tempArray copy]; [tempArray release]; return [returnArray autorelease]; } - (NSString *)uniqueID { // AppleScript: 'get id of ' (read-only). // NOTE: uniqueID is represented in the managed object model as a transient value, because it is derived from the managed object's built-in managed object ID. It is implemented primarily to support the AppleScript uniqueID reference form. return [[[self objectID] URIRepresentation] absoluteString]; } - (NSPersistentDocument *)document { // AppleScript: 'get document of object (read only). // Return the document that contains this object. // A managed object doesn't know anything about its document object except for its fileURL, so we find its document by comparing managed object contexts. This will give us access, through the document, to the user interface, if needed. NSManagedObjectContext *context = [self managedObjectContext]; NSEnumerator *enumerator = [[NSApp orderedDocuments] objectEnumerator]; NSPersistentDocument *document = nil; while ((document = [enumerator nextObject])) { if ([document managedObjectContext] == context) break; } return document; } - (NSScriptObjectSpecifier *)objectSpecifier { // Per the NSScriptObjectSpecifiers informal protocol. Returns a unique ID specifier specifying the absolute string of the URI representation of this managed object. Every scriptable class must implement -objectSpecifier. // AppleScript return value: 'company id '. // The primary container is the document containing this object's managed object context. NSPersistentDocument *document = [self document]; return (document == nil) ? nil : [[[NSUniqueIDSpecifier alloc] initWithContainerClassDescription:[[NSScriptSuiteRegistry sharedScriptSuiteRegistry] classDescriptionWithAppleEventCode:cDocument] containerSpecifier:[document objectSpecifier] key:[self appleScriptName] uniqueID:[self uniqueID]] autorelease]; } - (void) cloneAttributes: (NSManagedObject *) clone { NSArray *keys=[[[self entity] attributesByName] allKeys]; unsigned long i; for (i=0;i<[keys count];i++) { NSString *key=[keys objectAtIndex:i]; [clone setValue: [self valueForKey:key] forKey: key]; } } - (void) cloneRelationships: (NSManagedObject *) clone { NSDictionary *relationships=[[self entity] relationshipsByName]; NSArray *keys=[relationships allKeys]; unsigned long i; for (i=0;i<[keys count];i++) { NSString *key=[keys objectAtIndex:i]; if (![[relationships objectForKey: key] isToMany] || ![[relationships objectForKey: key] inverseRelationship]) [clone setValue: [self valueForKey:key] forKey: key]; } } - (NSManagedObject *) cloneOfSelf { NSManagedObject *clone=[NSEntityDescription insertNewObjectForEntityForName: [[self entity] name] inManagedObjectContext: [self managedObjectContext]]; [self cloneAttributes: clone]; [self cloneRelationships: clone]; return clone; } - (void) setScriptingProperties: (NSDictionary *) properties { NSMutableDictionary *newDictionary=[NSMutableDictionary dictionary]; NSEnumerator *keylist=[[properties allKeys] objectEnumerator]; NSString *key; while (key=[keylist nextObject]) { id result=[properties objectForKey:key]; if ([result isKindOfClass: [NSScriptObjectSpecifier class]]) { result=[((NSScriptObjectSpecifier *)[properties objectForKey:key]) objectsByEvaluatingSpecifier]; } [newDictionary setObject: result forKey:key]; } [super setScriptingProperties: newDictionary]; } - (NSDictionary *) currentValues { NSMutableDictionary *savedValues=[[self committedValuesForKeys:nil] mutableCopy]; [savedValues addEntriesFromDictionary: [self changedValues]]; return [savedValues autorelease]; } @end