mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
422 lines
15 KiB
Objective-C
422 lines
15 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 "FImmutableTree.h"
|
|
#import "FImmutableSortedDictionary.h"
|
|
#import "FPath.h"
|
|
#import "FUtilities.h"
|
|
|
|
@interface FImmutableTree ()
|
|
@property (nonatomic, strong, readwrite) id value;
|
|
/**
|
|
* Maps NSString -> FImmutableTree<T>, where <T> is type of value.
|
|
*/
|
|
@property (nonatomic, strong, readwrite) FImmutableSortedDictionary *children;
|
|
@end
|
|
|
|
@implementation FImmutableTree
|
|
@synthesize value;
|
|
@synthesize children;
|
|
|
|
- (id) initWithValue:(id)aValue {
|
|
self = [super init];
|
|
if (self) {
|
|
self.value = aValue;
|
|
self.children = [FImmutableTree emptyChildren];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id) initWithValue:(id)aValue children:(FImmutableSortedDictionary *)childrenMap {
|
|
self = [super init];
|
|
if (self) {
|
|
self.value = aValue;
|
|
self.children = childrenMap;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+ (FImmutableSortedDictionary *) emptyChildren {
|
|
static dispatch_once_t emptyChildrenToken;
|
|
static FImmutableSortedDictionary *emptyChildren;
|
|
dispatch_once(&emptyChildrenToken, ^{
|
|
emptyChildren = [FImmutableSortedDictionary dictionaryWithComparator:[FUtilities stringComparator]];
|
|
});
|
|
return emptyChildren;
|
|
}
|
|
|
|
+ (FImmutableTree *) empty {
|
|
static dispatch_once_t emptyImmutableTreeToken;
|
|
static FImmutableTree *emptyTree = nil;
|
|
dispatch_once(&emptyImmutableTreeToken, ^{
|
|
emptyTree = [[FImmutableTree alloc] initWithValue:nil];
|
|
});
|
|
return emptyTree;
|
|
}
|
|
|
|
- (BOOL) isEmpty {
|
|
return self.value == nil && [self.children isEmpty];
|
|
}
|
|
|
|
/**
|
|
* Given a path and a predicate, return the first node and the path to that node where the predicate returns true
|
|
* // TODO Do a perf test. If we're creating a bunch of FTuplePathValue objects on the way back out, it may be better to pass down a pathSoFar FPath
|
|
*/
|
|
- (FTuplePathValue *) findRootMostMatchingPath:(FPath *)relativePath predicate:(BOOL (^)(id value))predicate {
|
|
if (self.value != nil && predicate(self.value)) {
|
|
return [[FTuplePathValue alloc] initWithPath:[FPath empty] value:self.value];
|
|
} else {
|
|
if ([relativePath isEmpty]) {
|
|
return nil;
|
|
} else {
|
|
NSString *front = [relativePath getFront];
|
|
FImmutableTree *child = [self.children get:front];
|
|
if (child != nil) {
|
|
FTuplePathValue *childExistingPathAndValue = [child findRootMostMatchingPath:[relativePath popFront] predicate:predicate];
|
|
if (childExistingPathAndValue != nil) {
|
|
FPath *fullPath = [[[FPath alloc] initWith:front] child:childExistingPathAndValue.path];
|
|
return [[FTuplePathValue alloc] initWithPath:fullPath value:childExistingPathAndValue.value];
|
|
} else {
|
|
return nil;
|
|
}
|
|
} else {
|
|
// No child matching path
|
|
return nil;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find, if it exists, the shortest subpath of the given path that points a defined value in the tree
|
|
*/
|
|
- (FTuplePathValue *) findRootMostValueAndPath:(FPath *)relativePath {
|
|
return [self findRootMostMatchingPath:relativePath predicate:^BOOL(__unsafe_unretained id value){
|
|
return YES;
|
|
}];
|
|
}
|
|
|
|
- (id) rootMostValueOnPath:(FPath *)path {
|
|
return [self rootMostValueOnPath:path matching:^BOOL(id value) {
|
|
return YES;
|
|
}];
|
|
}
|
|
|
|
- (id) rootMostValueOnPath:(FPath *)path matching:(BOOL (^)(id))predicate {
|
|
if (self.value != nil && predicate(self.value)) {
|
|
return self.value;
|
|
} else if (path.isEmpty) {
|
|
return nil;
|
|
} else {
|
|
return [[self.children get:path.getFront] rootMostValueOnPath:[path popFront] matching:predicate];
|
|
}
|
|
}
|
|
|
|
- (id) leafMostValueOnPath:(FPath *)path {
|
|
return [self leafMostValueOnPath:path matching:^BOOL(id value) {
|
|
return YES;
|
|
}];
|
|
}
|
|
|
|
- (id) leafMostValueOnPath:(FPath *)relativePath matching:(BOOL (^)(id))predicate {
|
|
__block id currentValue = self.value;
|
|
__block FImmutableTree *currentTree = self;
|
|
[relativePath enumerateComponentsUsingBlock:^(NSString *key, BOOL *stop) {
|
|
currentTree = [currentTree.children get:key];
|
|
if (currentTree == nil) {
|
|
*stop = YES;
|
|
} else {
|
|
id treeValue = currentTree.value;
|
|
if (treeValue != nil && predicate(treeValue)) {
|
|
currentValue = treeValue;
|
|
}
|
|
}
|
|
}];
|
|
return currentValue;
|
|
}
|
|
|
|
- (BOOL) containsValueMatching:(BOOL (^)(id))predicate {
|
|
if (self.value != nil && predicate(self.value)) {
|
|
return YES;
|
|
} else {
|
|
__block BOOL found = NO;
|
|
[self.children enumerateKeysAndObjectsUsingBlock:^(NSString *key, FImmutableTree *subtree, BOOL *stop) {
|
|
found = [subtree containsValueMatching:predicate];
|
|
if (found) *stop = YES;
|
|
}];
|
|
return found;
|
|
}
|
|
}
|
|
|
|
- (FImmutableTree *) subtreeAtPath:(FPath *)relativePath {
|
|
if ([relativePath isEmpty]) {
|
|
return self;
|
|
} else {
|
|
NSString *front = [relativePath getFront];
|
|
FImmutableTree *childTree = [self.children get:front];
|
|
if (childTree != nil) {
|
|
return [childTree subtreeAtPath:[relativePath popFront]];
|
|
} else {
|
|
return [FImmutableTree empty];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets a value at the specified path
|
|
*/
|
|
- (FImmutableTree *) setValue:(id)newValue atPath:(FPath *)relativePath {
|
|
if ([relativePath isEmpty]) {
|
|
return [[FImmutableTree alloc] initWithValue:newValue children:self.children];
|
|
} else {
|
|
NSString *front = [relativePath getFront];
|
|
FImmutableTree *child = [self.children get:front];
|
|
if (child == nil) {
|
|
child = [FImmutableTree empty];
|
|
}
|
|
FImmutableTree *newChild = [child setValue:newValue atPath:[relativePath popFront]];
|
|
FImmutableSortedDictionary *newChildren = [self.children insertKey:front withValue:newChild];
|
|
return [[FImmutableTree alloc] initWithValue:self.value children:newChildren];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove the value at the specified path
|
|
*/
|
|
- (FImmutableTree *) removeValueAtPath:(FPath *)relativePath {
|
|
if ([relativePath isEmpty]) {
|
|
if ([self.children isEmpty]) {
|
|
return [FImmutableTree empty];
|
|
} else {
|
|
return [[FImmutableTree alloc] initWithValue:nil children:self.children];
|
|
}
|
|
} else {
|
|
NSString *front = [relativePath getFront];
|
|
FImmutableTree *child = [self.children get:front];
|
|
if (child) {
|
|
FImmutableTree *newChild = [child removeValueAtPath:[relativePath popFront]];
|
|
FImmutableSortedDictionary *newChildren;
|
|
if ([newChild isEmpty]) {
|
|
newChildren = [self.children removeKey:front];
|
|
} else {
|
|
newChildren = [self.children insertKey:front withValue:newChild];
|
|
}
|
|
if (self.value == nil && [newChildren isEmpty]) {
|
|
return [FImmutableTree empty];
|
|
} else {
|
|
return [[FImmutableTree alloc] initWithValue:self.value children:newChildren];
|
|
}
|
|
} else {
|
|
return self;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a value from the tree
|
|
*/
|
|
- (id) valueAtPath:(FPath *)relativePath {
|
|
if ([relativePath isEmpty]) {
|
|
return self.value;
|
|
} else {
|
|
NSString *front = [relativePath getFront];
|
|
FImmutableTree *child = [self.children get:front];
|
|
if (child) {
|
|
return [child valueAtPath:[relativePath popFront]];
|
|
} else {
|
|
return nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replaces the subtree at the specified path with the given new tree
|
|
*/
|
|
- (FImmutableTree *) setTree:(FImmutableTree *)newTree atPath:(FPath *)relativePath {
|
|
if ([relativePath isEmpty]) {
|
|
return newTree;
|
|
} else {
|
|
NSString *front = [relativePath getFront];
|
|
FImmutableTree *child = [self.children get:front];
|
|
if (child == nil) {
|
|
child = [FImmutableTree empty];
|
|
}
|
|
FImmutableTree *newChild = [child setTree:newTree atPath:[relativePath popFront]];
|
|
FImmutableSortedDictionary *newChildren;
|
|
if ([newChild isEmpty]) {
|
|
newChildren = [self.children removeKey:front];
|
|
} else {
|
|
newChildren = [self.children insertKey:front withValue:newChild];
|
|
}
|
|
return [[FImmutableTree alloc] initWithValue:self.value children:newChildren];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs a depth first fold on this tree. Transforms a tree into a single value, given a function that operates on
|
|
* the path to a node, an optional current value, and a map of the child names to folded subtrees
|
|
*/
|
|
- (id) foldWithBlock:(id (^)(FPath *path, id value, NSDictionary *foldedChildren))block {
|
|
return [self foldWithPathSoFar:[FPath empty] withBlock:block];
|
|
}
|
|
|
|
/**
|
|
* Recursive helper for public facing foldWithBlock: method
|
|
*/
|
|
- (id) foldWithPathSoFar:(FPath *)pathSoFar withBlock:(id (^)(FPath *path, id value, NSDictionary *foldedChildren))block {
|
|
__block NSMutableDictionary *accum = [[NSMutableDictionary alloc] init];
|
|
[self.children enumerateKeysAndObjectsUsingBlock:^(NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
|
|
accum[childKey] = [childTree foldWithPathSoFar:[pathSoFar childFromString:childKey] withBlock:block];
|
|
}];
|
|
return block(pathSoFar, self.value, accum);
|
|
}
|
|
|
|
/**
|
|
* Find the first matching value on the given path. Return the result of applying block to it.
|
|
*/
|
|
- (id) findOnPath:(FPath *)path andApplyBlock:(id (^)(FPath *path, id value))block {
|
|
return [self findOnPath:path pathSoFar:[FPath empty] andApplyBlock:block];
|
|
}
|
|
|
|
- (id) findOnPath:(FPath *)pathToFollow pathSoFar:(FPath *)pathSoFar andApplyBlock:(id (^)(FPath *path, id value))block {
|
|
id result = self.value ? block(pathSoFar, self.value) : nil;
|
|
if (result != nil) {
|
|
return result;
|
|
} else {
|
|
if ([pathToFollow isEmpty]) {
|
|
return nil;
|
|
} else {
|
|
NSString *front = [pathToFollow getFront];
|
|
FImmutableTree *nextChild = [self.children get:front];
|
|
if (nextChild != nil) {
|
|
return [nextChild findOnPath:[pathToFollow popFront] pathSoFar:[pathSoFar childFromString:front] andApplyBlock:block];
|
|
} else {
|
|
return nil;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Call the block on each value along the path for as long as that function returns true
|
|
* @return The path to the deepest location inspected
|
|
*/
|
|
- (FPath *) forEachOnPath:(FPath *)path whileBlock:(BOOL (^)(FPath *, id))block {
|
|
return [self forEachOnPath:path pathSoFar:[FPath empty] whileBlock:block];
|
|
}
|
|
|
|
- (FPath *) forEachOnPath:(FPath *)pathToFollow pathSoFar:(FPath *)pathSoFar whileBlock:(BOOL (^)(FPath *, id))block {
|
|
if ([pathToFollow isEmpty]) {
|
|
if (self.value) {
|
|
block(pathSoFar, self.value);
|
|
}
|
|
return pathSoFar;
|
|
} else {
|
|
BOOL shouldContinue = YES;
|
|
if (self.value) {
|
|
shouldContinue = block(pathSoFar, self.value);
|
|
}
|
|
if (shouldContinue) {
|
|
NSString *front = [pathToFollow getFront];
|
|
FImmutableTree *nextChild = [self.children get:front];
|
|
if (nextChild) {
|
|
return [nextChild forEachOnPath:[pathToFollow popFront] pathSoFar:[pathSoFar childFromString:front] whileBlock:block];
|
|
} else {
|
|
return pathSoFar;
|
|
}
|
|
} else {
|
|
return pathSoFar;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (FImmutableTree *) forEachOnPath:(FPath *)path performBlock:(void (^)(FPath *path, id value))block {
|
|
return [self forEachOnPath:path pathSoFar:[FPath empty] performBlock:block];
|
|
}
|
|
|
|
- (FImmutableTree *) forEachOnPath:(FPath *)pathToFollow pathSoFar:(FPath *)pathSoFar performBlock:(void (^)(FPath *path, id value))block {
|
|
if ([pathToFollow isEmpty]) {
|
|
return self;
|
|
} else {
|
|
if (self.value) {
|
|
block(pathSoFar, self.value);
|
|
}
|
|
NSString *front = [pathToFollow getFront];
|
|
FImmutableTree *nextChild = [self.children get:front];
|
|
if (nextChild) {
|
|
return [nextChild forEachOnPath:[pathToFollow popFront] pathSoFar:[pathSoFar childFromString:front] performBlock:block];
|
|
} else {
|
|
return [FImmutableTree empty];
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Calls the given block for each node in the tree that has a value. Called in depth-first order
|
|
*/
|
|
- (void) forEach:(void (^)(FPath *path, id value))block {
|
|
[self forEachPathSoFar:[FPath empty] withBlock:block];
|
|
}
|
|
|
|
- (void) forEachPathSoFar:(FPath *)pathSoFar withBlock:(void (^)(FPath *path, id value))block {
|
|
[self.children enumerateKeysAndObjectsUsingBlock:^(NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
|
|
[childTree forEachPathSoFar:[pathSoFar childFromString:childKey] withBlock:block];
|
|
}];
|
|
if (self.value) {
|
|
block(pathSoFar, self.value);
|
|
}
|
|
}
|
|
|
|
- (void) forEachChild:(void (^)(NSString *childKey, id childValue))block {
|
|
[self.children enumerateKeysAndObjectsUsingBlock:^(NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
|
|
if (childTree.value) {
|
|
block(childKey, childTree.value);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)object {
|
|
if (![object isKindOfClass:[FImmutableTree class]]) {
|
|
return NO;
|
|
}
|
|
FImmutableTree *other = (FImmutableTree *)object;
|
|
return (self.value == other.value || [self.value isEqual:other.value]) && [self.children isEqual:other.children];
|
|
}
|
|
|
|
- (NSUInteger)hash {
|
|
return self.children.hash * 31 + [self.value hash];
|
|
}
|
|
|
|
- (NSString *) description {
|
|
NSMutableString *string = [[NSMutableString alloc] init];
|
|
[string appendString:@"FImmutableTree { value="];
|
|
[string appendString:(self.value ? [self.value description] : @"<nil>")];
|
|
[string appendString:@", children={"];
|
|
[self.children enumerateKeysAndObjectsUsingBlock:^(NSString *childKey, FImmutableTree *childTree, BOOL *stop) {
|
|
[string appendString:@" "];
|
|
[string appendString:childKey];
|
|
[string appendString:@"="];
|
|
[string appendString:[childTree.value description]];
|
|
}];
|
|
[string appendString:@" } }"];
|
|
return [NSString stringWithString:string];
|
|
}
|
|
|
|
- (NSString *) debugDescription {
|
|
return [self description];
|
|
}
|
|
|
|
|
|
@end
|