Permalink
Browse files

Clean up the nasty mess in the visitor code and fix flaws in implemen…

…tation of algorithm. Still some test breakage and additional coverage to put down
  • Loading branch information...
1 parent 9a128b6 commit 1d27679ee368603695a99cc29577abbdf692079d @blakewatters blakewatters committed Dec 31, 2012
@@ -60,7 +60,7 @@ - (NSString *)description
The following reference implementations were used when building out an Objective-C implementation:
1. http://algowiki.net/wiki/index.php?title=Tarjan%27s_algorithm
- 1. http://www.logarithmic.net/pfh-files/blog/01208083168/sort.py
+ 1. http://www.logarithmic.net/pfh-files/blog/01208083168/tarjan.py
*/
@interface RKNestedManagedObjectKeyPathMappingGraphVisitor : NSObject
@@ -69,9 +69,10 @@ - (id)initWithResponseDescriptors:(NSArray *)responseDescriptors;
@end
@interface RKNestedManagedObjectKeyPathMappingGraphVisitor ()
+@property (nonatomic, assign) NSUInteger indexCounter;
@property (nonatomic, strong) NSMutableArray *visitationStack;
-@property (nonatomic, strong) NSMutableDictionary *lowValues;
-@property (nonatomic, strong) NSNumber *numberOfDecriptors;
+@property (nonatomic, strong) NSMutableDictionary *index;
+@property (nonatomic, strong) NSMutableDictionary *lowLinks;
@property (nonatomic, strong, readwrite) NSMutableArray *visitations;
@end
@@ -81,14 +82,17 @@ - (id)initWithResponseDescriptors:(NSArray *)responseDescriptors
{
self = [self init];
if (self) {
- self.numberOfDecriptors = @([responseDescriptors count]);
+ self.indexCounter = 0;
self.visitationStack = [NSMutableArray array];
- self.lowValues = [NSMutableDictionary dictionary];
+ self.index = [NSMutableDictionary dictionary];
+ self.lowLinks = [NSMutableDictionary dictionary];
self.visitations = [NSMutableArray array];
for (RKResponseDescriptor *responseDescriptor in responseDescriptors) {
+ self.indexCounter = 0;
[self.visitationStack removeAllObjects];
- [self.lowValues removeAllObjects];
+ [self.index removeAllObjects];
+ [self.lowLinks removeAllObjects];
[self visitMapping:responseDescriptor.mapping atKeyPath:responseDescriptor.keyPath];
}
}
@@ -116,11 +120,9 @@ - (RKMappingGraphVisitation *)visitationForMapping:(RKMapping *)mapping atKeyPat
- (void)visitMapping:(RKMapping *)mapping atKeyPath:(NSString *)keyPath
{
NSValue *dictionaryKey = [NSValue valueWithNonretainedObject:mapping];
- if ([self.lowValues objectForKey:dictionaryKey]) {
- if (![ mapping isKindOfClass:[RKEntityMapping class]]) return;
-
- NSArray *keyPathComponents = [[self.visitationStack valueForKey:@"keyPath"] arrayByAddingObject:keyPath];
- NSString *keyPath = [[keyPathComponents subarrayWithRange:NSMakeRange(1, [keyPathComponents count] - 1)] componentsJoinedByString:@"."];
+ // If a given mapping already appears within the lowValues, then we have a cycle in the graph
+ if ([self.lowLinks objectForKey:dictionaryKey]) {
+ if (![mapping isKindOfClass:[RKEntityMapping class]]) return;
RKMappingGraphVisitation *cyclicVisitation = [self visitationForMapping:mapping atKeyPath:keyPath];
cyclicVisitation.cyclic = YES;
@@ -129,55 +131,59 @@ - (void)visitMapping:(RKMapping *)mapping atKeyPath:(NSString *)keyPath
return;
}
- NSNumber *lowValue = @([self.lowValues count]);
- [self.lowValues setObject:lowValue forKey:dictionaryKey];
+ // Track the visit to each node in the graph. Note that we do not pop the stack as we traverse back up
+ [self.index setObject:@(self.indexCounter) forKey:dictionaryKey];
+ [self.lowLinks setObject:@(self.indexCounter) forKey:dictionaryKey];
+ self.indexCounter++;
- NSUInteger stackPosition = [self.visitationStack count];
RKMappingGraphVisitation *visitation = [self visitationForMapping:mapping atKeyPath:keyPath];
[self.visitationStack addObject:visitation];
if ([mapping isKindOfClass:[RKObjectMapping class]]) {
RKObjectMapping *objectMapping = (RKObjectMapping *)mapping;
for (RKRelationshipMapping *relationshipMapping in objectMapping.relationshipMappings) {
- [self visitMapping:relationshipMapping.mapping atKeyPath:relationshipMapping.destinationKeyPath];
-
- // We want the minimum value
+ // Check if the successor relationship appears in the lowlinks
NSValue *relationshipKey = [NSValue valueWithNonretainedObject:relationshipMapping.mapping];
- NSNumber *relationshipLowValue = [self.lowValues objectForKey:relationshipKey];
- if ([lowValue compare:relationshipLowValue] == NSOrderedDescending) {
- [self.lowValues setObject:relationshipLowValue forKey:dictionaryKey];
+ NSNumber *relationshipLowValue = [self.lowLinks objectForKey:relationshipKey];
+ if (relationshipLowValue == nil) {
+ // The relationship has not yet been visited, recurse
+ NSString *nestedKeyPath = ([self.visitationStack count] > 1 && keyPath) ? [@[ keyPath, relationshipMapping.destinationKeyPath ] componentsJoinedByString:@"."] : relationshipMapping.destinationKeyPath;
+ [self visitMapping:relationshipMapping.mapping atKeyPath:nestedKeyPath];
+
+ // Set the lowlink value for parent mapping to the lower value for us or the child mapping we just recursed on
+ NSNumber *lowLinkForMapping = [self.lowLinks objectForKey:dictionaryKey];
+ NSNumber *lowLinkForSuccessor = [self.lowLinks objectForKey:relationshipKey];
+
+ if ([lowLinkForMapping compare:lowLinkForSuccessor] == NSOrderedDescending) {
+ [self.lowLinks setObject:lowLinkForSuccessor forKey:dictionaryKey];
+ }
+ } else {
+ // The child mapping is already in the stack, so it is part of a strongly connected component
+ NSNumber *lowLinkForMapping = [self.lowLinks objectForKey:dictionaryKey];
+ NSNumber *indexValueForSuccessor = [self.index objectForKey:relationshipKey];
+ if ([lowLinkForMapping compare:indexValueForSuccessor] == NSOrderedDescending) {
+ [self.lowLinks setObject:indexValueForSuccessor forKey:dictionaryKey];
+ }
}
}
} else if ([mapping isKindOfClass:[RKDynamicMapping class]]) {
- // Pop the visitation stack to remove the dynamic mapping, since each mapping within the dynamic mapping
- // is rooted at the same point in the graph
- [self.visitationStack removeLastObject];
-
+ // Dynamic mappings appear at the same point in the graph, so we recurse with the same keyPath
for (RKMapping *nestedMapping in [(RKDynamicMapping *)mapping objectMappings]) {
[self visitMapping:nestedMapping atKeyPath:keyPath];
}
}
- if ([[self.lowValues objectForKey:dictionaryKey] isEqualToNumber:lowValue]) {
- NSRange range = NSMakeRange(stackPosition, [self.visitationStack count] - stackPosition);
- NSArray *visitations = [self.visitationStack subarrayWithRange:range];
- [self.visitationStack removeObjectsInRange:range];
+ // If the current mapping is a root node, then pop the stack to create an SCC
+ NSNumber *lowLinkValueForMapping = [self.lowLinks objectForKey:dictionaryKey];
+ NSNumber *indexValueForMapping = [self.index objectForKey:dictionaryKey];
+ if ([lowLinkValueForMapping isEqualToNumber:indexValueForMapping]) {
+ NSUInteger index = [self.visitationStack indexOfObject:visitation];
+ NSRange removalRange = NSMakeRange(index, [self.visitationStack count] - index);
+ [self.visitationStack removeObjectsInRange:removalRange];
- // Take everything left on the stack
- NSArray *keyPathComponents = [self.visitationStack valueForKey:@"keyPath"];
- NSString *nestingKeyPath = ([keyPathComponents count] > 1) ? [[keyPathComponents subarrayWithRange:NSMakeRange(1, [keyPathComponents count] - 1)] componentsJoinedByString:@"."] : nil;
-
- [visitations enumerateObjectsUsingBlock:^(RKMappingGraphVisitation *visitation, NSUInteger idx, BOOL *stop) {
- // If this is an entity mapping, collect the complete key path
- if ([visitation.mapping isKindOfClass:[RKEntityMapping class]]) {
- visitation.keyPath = nestingKeyPath ? [@[ nestingKeyPath, visitation.keyPath ] componentsJoinedByString:@"."] : visitation.keyPath;
- [self.visitations addObject:visitation];
- }
-
- // Update the low value
- NSValue *relationshipKey = [NSValue valueWithNonretainedObject:mapping];
- [self.lowValues setObject:self.numberOfDecriptors forKey:relationshipKey];
- }];
+ if ([visitation.mapping isKindOfClass:[RKEntityMapping class]]) {
+ [self.visitations addObject:visitation];
+ }
}
}
@@ -242,10 +248,10 @@ static void RKAddObjectsInGraphWithCyclicKeyPathsToMutableSet(id graph, NSSet *c
For example, given a set containing: 'this', 'this.that', 'another.one.test', 'another.two.test', 'another.one.test.nested'
would return: 'this, 'another.one', 'another.two'
*/
-NSOrderedSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths);
-NSOrderedSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths)
+NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths);
+NSSet *RKSetByRemovingSubkeypathsFromSet(NSSet *setOfKeyPaths)
{
- NSSet *prunedSet = [setOfKeyPaths objectsPassingTest:^BOOL(NSString *keyPath, BOOL *stop) {
+ return [setOfKeyPaths objectsPassingTest:^BOOL(NSString *keyPath, BOOL *stop) {
if ([keyPath isEqual:[NSNull null]]) return YES; // Special case the root key path
NSArray *keyPathComponents = [keyPath componentsSeparatedByString:@"."];
NSMutableSet *parentKeyPaths = [NSMutableSet set];
@@ -257,12 +263,6 @@ static void RKAddObjectsInGraphWithCyclicKeyPathsToMutableSet(id graph, NSSet *c
}
return YES;
}];
- NSMutableOrderedSet *orderedSet = [NSMutableOrderedSet orderedSetWithSet:prunedSet];
- if ([orderedSet containsObject:[NSNull null]]) {
- [orderedSet removeObject:[NSNull null]];
- [orderedSet setObject:[NSNull null] atIndex:0];
- }
- return orderedSet;
}
static void RKSetMappedValueForKeyPathInDictionary(id value, id rootKey, NSString *keyPath, NSMutableDictionary *dictionary)
@@ -297,80 +297,51 @@ static void RKSetMappedValueForKeyPathInDictionary(id value, id rootKey, NSStrin
static NSDictionary *RKDictionaryFromDictionaryWithManagedObjectsInVisitationsRefetchedInContext(NSDictionary *dictionaryOfManagedObjects, NSArray *visitations, NSManagedObjectContext *managedObjectContext)
{
if (! [dictionaryOfManagedObjects count]) return dictionaryOfManagedObjects;
+
NSMutableDictionary *newDictionary = [dictionaryOfManagedObjects mutableCopy];
[managedObjectContext performBlockAndWait:^{
NSSet *rootKeys = [NSSet setWithArray:[visitations valueForKey:@"rootKey"]];
for (id rootKey in rootKeys) {
- NSSet *keyPaths = [NSSet setWithArray:[visitations valueForKey:@"keyPath"]];
- NSOrderedSet *nonNestedKeyPaths = RKSetByRemovingSubkeypathsFromSet(keyPaths);
+ NSArray *visitationsForRootKey = [visitations filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"rootKey = %@", rootKey]];
+ NSSet *keyPaths = [visitationsForRootKey valueForKey:@"keyPath"];
+ // If keyPaths contains null, then the root object is a managed object and we only need to refetch it
+ NSSet *nonNestedKeyPaths = ([keyPaths containsObject:[NSNull null]]) ? [NSSet setWithObject:[NSNull null]] : RKSetByRemovingSubkeypathsFromSet(keyPaths);
NSDictionary *mappingResultsAtRootKey = [dictionaryOfManagedObjects objectForKey:rootKey];
for (NSString *keyPath in nonNestedKeyPaths) {
- __block BOOL refetched = NO;
-
- if (! [keyPath isEqual:[NSNull null]]) {
- if ([mappingResultsAtRootKey conformsToProtocol:@protocol(NSFastEnumeration)]) {
- // This is a collection
- BOOL respondsToSelector = YES;
- for (id object in mappingResultsAtRootKey) {
- if (! [object respondsToSelector:NSSelectorFromString(keyPath)]) respondsToSelector = NO;
- }
-
- if (! respondsToSelector) continue;
- } else {
- if (! [mappingResultsAtRootKey respondsToSelector:NSSelectorFromString(keyPath)]) {
- continue;
- }
- }
- }
id value = [keyPath isEqual:[NSNull null]] ? mappingResultsAtRootKey : [mappingResultsAtRootKey valueForKeyPath:keyPath];
if (! value) {
continue;
} else if ([value isKindOfClass:[NSArray class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableArray class]];
NSMutableArray *newValue = [[NSMutableArray alloc] initWithCapacity:[value count]];
for (__strong id object in value) {
- if ([object isKindOfClass:[NSManagedObject class]]) {
- object = RKRefetchManagedObjectInContext(object, managedObjectContext);
- refetched = YES;
- }
+ if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
if (object) [newValue addObject:object];
}
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSSet class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableSet class]];
NSMutableSet *newValue = [[NSMutableSet alloc] initWithCapacity:[value count]];
for (__strong id object in value) {
- if ([object isKindOfClass:[NSManagedObject class]]) {
- object = RKRefetchManagedObjectInContext(object, managedObjectContext);
- refetched = YES;
- }
+ if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
if (object) [newValue addObject:object];
}
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSOrderedSet class]]) {
BOOL isMutable = [value isKindOfClass:[NSMutableOrderedSet class]];
NSMutableOrderedSet *newValue = [NSMutableOrderedSet orderedSet];
[(NSOrderedSet *)value enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
- if ([object isKindOfClass:[NSManagedObject class]]) {
- object = RKRefetchManagedObjectInContext(object, managedObjectContext);
- refetched = YES;
- }
+ if ([object isKindOfClass:[NSManagedObject class]]) object = RKRefetchManagedObjectInContext(object, managedObjectContext);
if (object) [newValue setObject:object atIndex:index];
}];
value = (isMutable) ? newValue : [newValue copy];
} else if ([value isKindOfClass:[NSManagedObject class]]) {
value = RKRefetchManagedObjectInContext(value, managedObjectContext);
- refetched = YES;
}
if (value) {
RKSetMappedValueForKeyPathInDictionary(value, rootKey, keyPath, newDictionary);
-
- // If we have refetched the root object, then we are done
- if (keyPath == [NSNull null] && refetched) {
- break;
- }
}
}
}
@@ -660,7 +631,7 @@ - (void)willFinish
NSError *error = nil;
// Construct a set of key paths to all of the managed objects in the mapping result
- RKNestedManagedObjectKeyPathMappingGraphVisitor *visitor = [[RKNestedManagedObjectKeyPathMappingGraphVisitor alloc] initWithResponseDescriptors:self.responseDescriptors];
+ RKNestedManagedObjectKeyPathMappingGraphVisitor *visitor = [[RKNestedManagedObjectKeyPathMappingGraphVisitor alloc] initWithResponseDescriptors:self.responseMapperOperation.matchingResponseDescriptors];
// Handle any cleanup
success = [self deleteTargetObjectIfAppropriate:&error];
@@ -114,6 +114,14 @@
*/
@property (nonatomic, strong, readonly) NSDictionary *responseMappingsDictionary;
+/**
+ Returns an array containing all `RKResponseDescriptor` objects in the configured `responseDescriptors` array that were found to match response.
+
+ @see `responseDescriptors`
+ @see `RKResponseDescriptor`
+ */
+@property (nonatomic, strong, readonly) NSArray *matchingResponseDescriptors;
+
///--------------------------------
/// @name Accessing Mapping Results
///--------------------------------
@@ -110,6 +110,7 @@ @interface RKResponseMapperOperation ()
@property (nonatomic, strong, readwrite) NSArray *responseDescriptors;
@property (nonatomic, strong, readwrite) RKMappingResult *mappingResult;
@property (nonatomic, strong, readwrite) NSError *error;
+@property (nonatomic, strong, readwrite) NSArray *matchingResponseDescriptors;
@property (nonatomic, strong, readwrite) NSDictionary *responseMappingsDictionary;
@property (nonatomic, strong) RKMapperOperation *mapperOperation;
@property (nonatomic, copy) id (^willMapDeserializedResponseBlock)(id deserializedResponseBody);
@@ -133,6 +134,7 @@ - (id)initWithResponse:(NSHTTPURLResponse *)response data:(NSData *)data respons
self.response = response;
self.data = data;
self.responseDescriptors = responseDescriptors;
+ self.matchingResponseDescriptors = [self buildMatchingResponseDescriptors];
self.responseMappingsDictionary = [self buildResponseMappingsDictionary];
self.treatsEmptyResponseAsSuccess = YES;
}
@@ -163,14 +165,19 @@ - (id)parseResponseData:(NSError **)error
return object;
}
+- (NSArray *)buildMatchingResponseDescriptors
+{
+ NSIndexSet *indexSet = [self.responseDescriptors indexesOfObjectsPassingTest:^BOOL(RKResponseDescriptor *responseDescriptor, NSUInteger idx, BOOL *stop) {
+ return [responseDescriptor matchesResponse:self.response];
+ }];
+ return [self.responseDescriptors objectsAtIndexes:indexSet];
+}
+
- (NSDictionary *)buildResponseMappingsDictionary
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
- for (RKResponseDescriptor *responseDescriptor in self.responseDescriptors) {
- if ([responseDescriptor matchesResponse:self.response]) {
- id key = responseDescriptor.keyPath ? responseDescriptor.keyPath : [NSNull null];
- [dictionary setObject:responseDescriptor.mapping forKey:key];
- }
+ for (RKResponseDescriptor *responseDescriptor in self.matchingResponseDescriptors) {
+ [dictionary setObject:responseDescriptor.mapping forKey:(responseDescriptor.keyPath ?: [NSNull null])];
}
return dictionary;

0 comments on commit 1d27679

Please sign in to comment.