mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
419 lines
14 KiB
Objective-C
419 lines
14 KiB
Objective-C
/*
|
|
* 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 "FirebaseDatabase/Sources/Snapshot/FChildrenNode.h"
|
|
#import "FirebaseDatabase/Sources/Constants/FConstants.h"
|
|
#import "FirebaseDatabase/Sources/FMaxNode.h"
|
|
#import "FirebaseDatabase/Sources/FNamedNode.h"
|
|
#import "FirebaseDatabase/Sources/FPriorityIndex.h"
|
|
#import "FirebaseDatabase/Sources/FTransformedEnumerator.h"
|
|
#import "FirebaseDatabase/Sources/Snapshot/FEmptyNode.h"
|
|
#import "FirebaseDatabase/Sources/Snapshot/FSnapshotUtilities.h"
|
|
#import "FirebaseDatabase/Sources/Utilities/FStringUtilities.h"
|
|
#import "FirebaseDatabase/Sources/Utilities/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
|