| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 |
- /*
- * 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 "FConstants.h"
- #import "FEmptyNode.h"
- #import "FMaxNode.h"
- #import "FNamedNode.h"
- #import "FPriorityIndex.h"
- #import "FSnapshotUtilities.h"
- #import "FStringUtilities.h"
- #import "FTransformedEnumerator.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<FNode>)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<FNode>)getPriority {
- if (self.priorityNode) {
- return self.priorityNode;
- } else {
- return [FEmptyNode emptyNode];
- }
- }
- - (id<FNode>)updatePriority:(id<FNode>)aPriority {
- if ([self.children isEmpty]) {
- return [FEmptyNode emptyNode];
- } else {
- return [[FChildrenNode alloc] initWithPriority:aPriority
- children:self.children];
- }
- }
- - (id<FNode>)getImmediateChild:(NSString *)childName {
- if ([childName isEqualToString:@".priority"]) {
- return [self getPriority];
- } else {
- id<FNode> child = [self.children objectForKey:childName];
- return (child == nil) ? [FEmptyNode emptyNode] : child;
- }
- }
- - (id<FNode>)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<FNode>)updateImmediateChild:(NSString *)childName
- withNewChild:(id<FNode>)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<FNode>)updateChild:(FPath *)path withNewChild:(id<FNode>)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<FNode> 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<FNode> 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<FNode> node,
- BOOL *stop) {
- sawPriority = sawPriority || [[node getPriority] isEmpty];
- *stop = sawPriority;
- }];
- if (sawPriority) {
- NSMutableArray *array = [NSMutableArray array];
- [self enumerateChildrenUsingBlock:^(NSString *key, id<FNode> 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<FNode> 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<FNode>)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<FNode>)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<FNode> node,
- BOOL *stop) {
- id<FNode> 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<FNode> node,
- BOOL *stop) {
- hashCode = 31 * hashCode + key.hash;
- hashCode = 17 * hashCode + node.hash;
- }];
- return 17 * hashCode + self.priorityNode.hash;
- }
- - (void)enumerateChildrenAndPriorityUsingBlock:(void (^)(NSString *, id<FNode>,
- BOOL *))block {
- if ([self.getPriority isEmpty]) {
- [self enumerateChildrenUsingBlock:block];
- } else {
- __block BOOL passedPriorityKey = NO;
- [self enumerateChildrenUsingBlock:^(NSString *key, id<FNode> 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<FNode>,
- BOOL *))block {
- [self.children enumerateKeysAndObjectsUsingBlock:block];
- }
- - (void)enumerateChildrenReverse:(BOOL)reverse
- usingBlock:
- (void (^)(NSString *, id<FNode>, 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
|