| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- /*
- * 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
|