/* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "FChildrenNode.h" #import "FEmptyNode.h" #import "FConstants.h" #import "FStringUtilities.h" #import "FUtilities.h" #import "FNamedNode.h" #import "FMaxNode.h" #import "FTransformedEnumerator.h" #import "FSnapshotUtilities.h" #import "FTransformedEnumerator.h" #import "FPriorityIndex.h" #import "FUtilities.h" @interface FChildrenNode () @property (nonatomic, strong) NSString *lazyHash; @end @implementation FChildrenNode // Note: The only reason we allow nil priority is to for EmptyNode, since we can't use // EmptyNode as the priority of EmptyNode. We might want to consider making EmptyNode its own // class instead of an empty ChildrenNode. - (id)init { return [self initWithPriority:nil children:[FImmutableSortedDictionary dictionaryWithComparator:[FUtilities keyComparator]]]; } - (id)initWithChildren:(FImmutableSortedDictionary *)someChildren { return [self initWithPriority:nil children:someChildren]; } - (id)initWithPriority:(id)aPriority children:(FImmutableSortedDictionary *)someChildren { if (someChildren.isEmpty && aPriority != nil && ![aPriority isEmpty]) { [NSException raise:NSInvalidArgumentException format:@"Can't create empty node with priority!"]; } self = [super init]; if(self) { self.children = someChildren; self.priorityNode = aPriority; } return self; } - (NSString *) description { return [[self valForExport:YES] description]; } #pragma mark - #pragma mark FNode methods - (BOOL) isLeafNode { return NO; } - (id) getPriority { if (self.priorityNode) { return self.priorityNode; } else { return [FEmptyNode emptyNode]; } } - (id) updatePriority:(id)aPriority { if ([self.children isEmpty]) { return [FEmptyNode emptyNode]; } else { return [[FChildrenNode alloc] initWithPriority:aPriority children:self.children]; } } - (id) getImmediateChild:(NSString *) childName { if ([childName isEqualToString:@".priority"]) { return [self getPriority]; } else { id child = [self.children objectForKey:childName]; return (child == nil) ? [FEmptyNode emptyNode] : child; } } - (id) getChild:(FPath *)path { NSString* front = [path getFront]; if(front == nil) { return self; } else { return [[self getImmediateChild:front] getChild:[path popFront]]; } } - (BOOL)hasChild:(NSString *)childName { return ![self getImmediateChild:childName].isEmpty; } - (id) updateImmediateChild:(NSString *)childName withNewChild:(id)newChildNode { NSAssert(newChildNode != nil, @"Should always be passing nodes."); if ([childName isEqualToString:@".priority"]) { return [self updatePriority:newChildNode]; } else { FImmutableSortedDictionary *newChildren; if (newChildNode.isEmpty) { newChildren = [self.children removeObjectForKey:childName]; } else { newChildren = [self.children setObject:newChildNode forKey:childName]; } if (newChildren.isEmpty) { return [FEmptyNode emptyNode]; } else { return [[FChildrenNode alloc] initWithPriority:self.getPriority children:newChildren]; } } } - (id) updateChild:(FPath *)path withNewChild:(id)newChildNode { NSString* front = [path getFront]; if(front == nil) { return newChildNode; } else { NSAssert(![front isEqualToString:@".priority"] || path.length == 1, @".priority must be the last token in a path."); id newImmediateChild = [[self getImmediateChild:front] updateChild:[path popFront] withNewChild:newChildNode]; return [self updateImmediateChild:front withNewChild:newImmediateChild]; } } - (BOOL) isEmpty { return [self.children isEmpty]; } - (int) numChildren { return [self.children count]; } - (id) val { return [self valForExport:NO]; } - (id) valForExport:(BOOL)exp { if([self isEmpty]) { return [NSNull null]; } __block int numKeys = 0; __block NSInteger maxKey = 0; __block BOOL allIntegerKeys = YES; NSMutableDictionary* obj = [[NSMutableDictionary alloc] initWithCapacity:[self.children count]]; [self enumerateChildrenUsingBlock:^(NSString *key, id childNode, BOOL *stop) { [obj setObject:[childNode valForExport:exp] forKey:key]; numKeys++; // If we already found a string key, don't bother with any of this if (!allIntegerKeys) { return; } // Treat leading zeroes that are not exactly "0" as strings NSString* firstChar = [key substringWithRange:NSMakeRange(0, 1)]; if ([firstChar isEqualToString:@"0"] && [key length] > 1) { allIntegerKeys = NO; } else { NSNumber *keyAsNum = [FUtilities intForString:key]; if (keyAsNum != nil) { NSInteger keyAsInt = [keyAsNum integerValue]; if (keyAsInt > maxKey) { maxKey = keyAsInt; } } else { allIntegerKeys = NO; } } }]; if (!exp && allIntegerKeys && maxKey < 2 * numKeys) { // convert to an array NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:maxKey + 1]; for (int i = 0; i <= maxKey; ++i) { NSString* keyString = [NSString stringWithFormat:@"%i", i]; id child = obj[keyString]; if (child != nil) { [array addObject:child]; } else { [array addObject:[NSNull null]]; } } return array; } else { if(exp && [self getPriority] != nil && !self.getPriority.isEmpty) { obj[kPayloadPriority] = [self.getPriority val]; } return obj; } } - (NSString *) dataHash { if (self.lazyHash == nil) { NSMutableString *toHash = [[NSMutableString alloc] init]; if (!self.getPriority.isEmpty) { [toHash appendString:@"priority:"]; [FSnapshotUtilities appendHashRepresentationForLeafNode:(FLeafNode *)self.getPriority toString:toHash hashVersion:FDataHashVersionV1]; [toHash appendString:@":"]; } __block BOOL sawPriority = NO; [self enumerateChildrenUsingBlock:^(NSString *key, id node, BOOL *stop) { sawPriority = sawPriority || [[node getPriority] isEmpty]; *stop = sawPriority; }]; if (sawPriority) { NSMutableArray *array = [NSMutableArray array]; [self enumerateChildrenUsingBlock:^(NSString *key, id node, BOOL *stop) { FNamedNode *namedNode = [[FNamedNode alloc] initWithName:key andNode:node]; [array addObject:namedNode]; }]; [array sortUsingComparator:^NSComparisonResult(FNamedNode *namedNode1, FNamedNode *namedNode2) { return [[FPriorityIndex priorityIndex] compareNamedNode:namedNode1 toNamedNode:namedNode2]; }]; [array enumerateObjectsUsingBlock:^(FNamedNode *namedNode, NSUInteger idx, BOOL *stop) { NSString *childHash = [namedNode.node dataHash]; if (![childHash isEqualToString:@""]) { [toHash appendFormat:@":%@:%@", namedNode.name, childHash]; } }]; } else { [self enumerateChildrenUsingBlock:^(NSString *key, id node, BOOL *stop) { NSString *childHash = [node dataHash]; if (![childHash isEqualToString:@""]) { [toHash appendFormat:@":%@:%@", key, childHash]; } }]; } self.lazyHash = [toHash isEqualToString:@""] ? @"" : [FStringUtilities base64EncodedSha1:toHash]; } return self.lazyHash; } - (NSComparisonResult)compare:(id )other { // children nodes come last, unless this is actually an empty node, then we come first. if (self.isEmpty) { if (other.isEmpty) { return NSOrderedSame; } else { return NSOrderedAscending; } } else if (other.isLeafNode || other.isEmpty) { return NSOrderedDescending; } else if (other == [FMaxNode maxNode]) { return NSOrderedAscending; } else { // Must be another node with children. return NSOrderedSame; } } - (BOOL)isEqual:(id )other { if (other == self) { return YES; } else if (other == nil) { return NO; } else if (other.isLeafNode) { return NO; } else if (self.isEmpty && [other isEmpty]) { // Empty nodes do not have priority return YES; } else { FChildrenNode *otherChildrenNode = other; if (![self.getPriority isEqual:other.getPriority]) { return NO; } else if (self.children.count == otherChildrenNode.children.count) { __block BOOL equal = YES; [self enumerateChildrenUsingBlock:^(NSString *key, id node, BOOL *stop) { id child = [otherChildrenNode getImmediateChild:key]; if (![child isEqual:node]) { equal = NO; *stop = YES; } }]; return equal; } else { return NO; } } } - (NSUInteger)hash { __block NSUInteger hashCode = 0; [self enumerateChildrenUsingBlock:^(NSString *key, id node, BOOL *stop) { hashCode = 31 * hashCode + key.hash; hashCode = 17 * hashCode + node.hash; }]; return 17 * hashCode + self.priorityNode.hash; } - (void) enumerateChildrenAndPriorityUsingBlock:(void (^)(NSString *, id, BOOL *))block { if ([self.getPriority isEmpty]) { [self enumerateChildrenUsingBlock:block]; } else { __block BOOL passedPriorityKey = NO; [self enumerateChildrenUsingBlock:^(NSString *key, id node, BOOL *stop) { if (!passedPriorityKey && [FUtilities compareKey:key toKey:@".priority"] == NSOrderedDescending) { passedPriorityKey = YES; BOOL stopAfterPriority = NO; block(@".priority", [self getPriority], &stopAfterPriority); if (stopAfterPriority) return; } block(key, node, stop); }]; } } - (void) enumerateChildrenUsingBlock:(void (^)(NSString *, id, BOOL *))block { [self.children enumerateKeysAndObjectsUsingBlock:block]; } - (void) enumerateChildrenReverse:(BOOL)reverse usingBlock:(void (^)(NSString *, id, BOOL *))block { [self.children enumerateKeysAndObjectsReverse:reverse usingBlock:block]; } - (NSEnumerator *)childEnumerator { return [[FTransformedEnumerator alloc] initWithEnumerator:self.children.keyEnumerator andTransform:^id(NSString *key) { return [FNamedNode nodeWithName:key node:[self getImmediateChild:key]]; }]; } - (NSString *) predecessorChildKey:(NSString *)childKey { return [self.children getPredecessorKey:childKey]; } #pragma mark - #pragma mark FChildrenNode specific methods - (id) childrenGetter:(id)key { return [self.children objectForKey:key]; } - (FNamedNode *)firstChild { NSString *childKey = self.children.minKey; if (childKey) { return [[FNamedNode alloc] initWithName:childKey andNode:[self getImmediateChild:childKey]]; } else { return nil; } } - (FNamedNode *)lastChild { NSString *childKey = self.children.maxKey; if (childKey) { return [[FNamedNode alloc] initWithName:childKey andNode:[self getImmediateChild:childKey]]; } else { return nil; } } @end