added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMManagedArray.mm
diff --git a/iOS/Pods/Realm/Realm/RLMManagedArray.mm b/iOS/Pods/Realm/Realm/RLMManagedArray.mm
new file mode 100644 (file)
index 0000000..2593618
--- /dev/null
@@ -0,0 +1,545 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2014 Realm Inc.
+//
+// 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 "RLMArray_Private.hpp"
+
+#import "RLMAccessor.hpp"
+#import "RLMObjectSchema_Private.hpp"
+#import "RLMObjectStore.h"
+#import "RLMObject_Private.hpp"
+#import "RLMObservation.hpp"
+#import "RLMProperty_Private.h"
+#import "RLMQueryUtil.hpp"
+#import "RLMRealm_Private.hpp"
+#import "RLMSchema.h"
+#import "RLMThreadSafeReference_Private.hpp"
+#import "RLMUtil.hpp"
+
+#import "list.hpp"
+#import "results.hpp"
+#import "shared_realm.hpp"
+
+#import <realm/table_view.hpp>
+#import <objc/runtime.h>
+
+@interface RLMManagedArrayHandoverMetadata : NSObject
+@property (nonatomic) NSString *parentClassName;
+@property (nonatomic) NSString *key;
+@end
+
+@implementation RLMManagedArrayHandoverMetadata
+@end
+
+@interface RLMManagedArray () <RLMThreadConfined_Private>
+@end
+
+//
+// RLMArray implementation
+//
+@implementation RLMManagedArray {
+@public
+    realm::List _backingList;
+    RLMRealm *_realm;
+    RLMClassInfo *_objectInfo;
+    RLMClassInfo *_ownerInfo;
+    std::unique_ptr<RLMObservationInfo> _observationInfo;
+}
+
+- (RLMManagedArray *)initWithList:(realm::List)list
+                            realm:(__unsafe_unretained RLMRealm *const)realm
+                       parentInfo:(RLMClassInfo *)parentInfo
+                         property:(__unsafe_unretained RLMProperty *const)property {
+    if (property.type == RLMPropertyTypeObject)
+        self = [self initWithObjectClassName:property.objectClassName];
+    else
+        self = [self initWithObjectType:property.type optional:property.optional];
+    if (self) {
+        _realm = realm;
+        REALM_ASSERT(list.get_realm() == realm->_realm);
+        _backingList = std::move(list);
+        _ownerInfo = parentInfo;
+        if (property.type == RLMPropertyTypeObject)
+            _objectInfo = &parentInfo->linkTargetType(property.index);
+        else
+            _objectInfo = _ownerInfo;
+        _key = property.name;
+    }
+    return self;
+}
+
+- (RLMManagedArray *)initWithParent:(__unsafe_unretained RLMObjectBase *const)parentObject
+                           property:(__unsafe_unretained RLMProperty *const)property {
+    __unsafe_unretained RLMRealm *const realm = parentObject->_realm;
+    auto col = parentObject->_info->tableColumn(property);
+    auto& row = parentObject->_row;
+    return [self initWithList:realm::List(realm->_realm, *row.get_table(), col, row.get_index())
+                        realm:realm
+                   parentInfo:parentObject->_info
+                     property:property];
+}
+
+void RLMValidateArrayObservationKey(__unsafe_unretained NSString *const keyPath,
+                                    __unsafe_unretained RLMArray *const array) {
+    if (![keyPath isEqualToString:RLMInvalidatedKey]) {
+        @throw RLMException(@"[<%@ %p> addObserver:forKeyPath:options:context:] is not supported. Key path: %@",
+                            [array class], array, keyPath);
+    }
+}
+
+void RLMEnsureArrayObservationInfo(std::unique_ptr<RLMObservationInfo>& info,
+                                   __unsafe_unretained NSString *const keyPath,
+                                   __unsafe_unretained RLMArray *const array,
+                                   __unsafe_unretained id const observed) {
+    RLMValidateArrayObservationKey(keyPath, array);
+    if (!info && array.class == [RLMManagedArray class]) {
+        auto lv = static_cast<RLMManagedArray *>(array);
+        info = std::make_unique<RLMObservationInfo>(*lv->_ownerInfo,
+                                                    lv->_backingList.get_origin_row_index(),
+                                                    observed);
+    }
+}
+
+//
+// validation helpers
+//
+[[gnu::noinline]]
+[[noreturn]]
+static void throwError(__unsafe_unretained RLMManagedArray *const ar, NSString *aggregateMethod) {
+    try {
+        throw;
+    }
+    catch (realm::InvalidTransactionException const&) {
+        @throw RLMException(@"Cannot modify managed RLMArray outside of a write transaction.");
+    }
+    catch (realm::IncorrectThreadException const&) {
+        @throw RLMException(@"Realm accessed from incorrect thread.");
+    }
+    catch (realm::List::InvalidatedException const&) {
+        @throw RLMException(@"RLMArray has been invalidated or the containing object has been deleted.");
+    }
+    catch (realm::List::OutOfBoundsIndexException const& e) {
+        @throw RLMException(@"Index %zu is out of bounds (must be less than %zu).",
+                            e.requested, e.valid_count);
+    }
+    catch (realm::Results::UnsupportedColumnTypeException const& e) {
+        if (ar->_backingList.get_type() == realm::PropertyType::Object) {
+            @throw RLMException(@"%@: is not supported for %s%s property '%s'.",
+                                aggregateMethod,
+                                string_for_property_type(e.property_type),
+                                is_nullable(e.property_type) ? "?" : "",
+                                e.column_name.data());
+        }
+        @throw RLMException(@"%@: is not supported for %s%s array '%@.%@'.",
+                            aggregateMethod,
+                            string_for_property_type(e.property_type),
+                            is_nullable(e.property_type) ? "?" : "",
+                            ar->_ownerInfo->rlmObjectSchema.className, ar->_key);
+    }
+    catch (std::logic_error const& e) {
+        @throw RLMException(e);
+    }
+}
+
+template<typename Function>
+static auto translateErrors(__unsafe_unretained RLMManagedArray *const ar,
+                            Function&& f, NSString *aggregateMethod=nil) {
+    try {
+        return f();
+    }
+    catch (...) {
+        throwError(ar, aggregateMethod);
+    }
+}
+
+template<typename Function>
+static auto translateErrors(Function&& f) {
+    try {
+        return f();
+    }
+    catch (...) {
+        throwError(nil, nil);
+    }
+}
+
+template<typename IndexSetFactory>
+static void changeArray(__unsafe_unretained RLMManagedArray *const ar,
+                        NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
+    translateErrors([&] { ar->_backingList.verify_in_transaction(); });
+    RLMObservationInfo *info = RLMGetObservationInfo(ar->_observationInfo.get(),
+                                                     ar->_backingList.get_origin_row_index(),
+                                                     *ar->_ownerInfo);
+    if (info) {
+        NSIndexSet *indexes = is();
+        info->willChange(ar->_key, kind, indexes);
+        try {
+            f();
+        }
+        catch (...) {
+            info->didChange(ar->_key, kind, indexes);
+            throwError(ar, nil);
+        }
+        info->didChange(ar->_key, kind, indexes);
+    }
+    else {
+        translateErrors([&] { f(); });
+    }
+}
+
+static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSUInteger index, dispatch_block_t f) {
+    changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; });
+}
+
+static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSRange range, dispatch_block_t f) {
+    changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; });
+}
+
+static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSIndexSet *is, dispatch_block_t f) {
+    changeArray(ar, kind, f, [=] { return is; });
+}
+
+//
+// public method implementations
+//
+- (RLMRealm *)realm {
+    return _realm;
+}
+
+- (NSUInteger)count {
+    return translateErrors([&] { return _backingList.size(); });
+}
+
+- (BOOL)isInvalidated {
+    return translateErrors([&] { return !_backingList.is_valid(); });
+}
+
+- (RLMClassInfo *)objectInfo {
+    return _objectInfo;
+}
+
+
+- (bool)isBackedByList:(realm::List const&)list {
+    return _backingList == list;
+}
+
+- (BOOL)isEqual:(id)object {
+    return [object respondsToSelector:@selector(isBackedByList:)] && [object isBackedByList:_backingList];
+}
+
+- (NSUInteger)hash {
+    return std::hash<realm::List>()(_backingList);
+}
+
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
+                                  objects:(__unused __unsafe_unretained id [])buffer
+                                    count:(NSUInteger)len {
+    return RLMFastEnumerate(state, len, self);
+}
+
+- (id)objectAtIndex:(NSUInteger)index {
+    return translateErrors([&] {
+        RLMAccessorContext context(_realm, *_objectInfo);
+        return _backingList.get(context, index);
+    });
+}
+
+static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) {
+    RLMArrayValidateMatchingObjectType(ar, object);
+    if (index == NSUIntegerMax) {
+        index = translateErrors([&] { return ar->_backingList.size(); });
+    }
+
+    changeArray(ar, NSKeyValueChangeInsertion, index, ^{
+        RLMAccessorContext context(ar->_realm, *ar->_objectInfo);
+        ar->_backingList.insert(context, index, object);
+    });
+}
+
+- (void)addObject:(id)object {
+    RLMInsertObject(self, object, NSUIntegerMax);
+}
+
+- (void)insertObject:(id)object atIndex:(NSUInteger)index {
+    RLMInsertObject(self, object, index);
+}
+
+- (void)insertObjects:(id<NSFastEnumeration>)objects atIndexes:(NSIndexSet *)indexes {
+    changeArray(self, NSKeyValueChangeInsertion, indexes, ^{
+        NSUInteger index = [indexes firstIndex];
+        RLMAccessorContext context(_realm, *_objectInfo);
+        for (id obj in objects) {
+            RLMArrayValidateMatchingObjectType(self, obj);
+            _backingList.insert(context, index, obj);
+            index = [indexes indexGreaterThanIndex:index];
+        }
+    });
+}
+
+
+- (void)removeObjectAtIndex:(NSUInteger)index {
+    changeArray(self, NSKeyValueChangeRemoval, index, ^{
+        _backingList.remove(index);
+    });
+}
+
+- (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
+    changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
+        [indexes enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *) {
+            _backingList.remove(idx);
+        }];
+    });
+}
+
+- (void)addObjectsFromArray:(NSArray *)array {
+    changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(self.count, array.count), ^{
+        RLMAccessorContext context(_realm, *_objectInfo);
+        for (id obj in array) {
+            RLMArrayValidateMatchingObjectType(self, obj);
+            _backingList.add(context, obj);
+        }
+    });
+}
+
+- (void)removeAllObjects {
+    changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, self.count), ^{
+        _backingList.remove_all();
+    });
+}
+
+- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)object {
+    RLMArrayValidateMatchingObjectType(self, object);
+    changeArray(self, NSKeyValueChangeReplacement, index, ^{
+        RLMAccessorContext context(_realm, *_objectInfo);
+        _backingList.set(context, index, object);
+    });
+}
+
+- (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
+    auto start = std::min(sourceIndex, destinationIndex);
+    auto len = std::max(sourceIndex, destinationIndex) - start + 1;
+    changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{
+        _backingList.move(sourceIndex, destinationIndex);
+    });
+}
+
+- (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
+    changeArray(self, NSKeyValueChangeReplacement, ^{
+        _backingList.swap(index1, index2);
+    }, [=] {
+        NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
+        [set addIndex:index2];
+        return set;
+    });
+}
+
+- (NSUInteger)indexOfObject:(id)object {
+    RLMArrayValidateMatchingObjectType(self, object);
+    return translateErrors([&] {
+        RLMAccessorContext context(_realm, *_objectInfo);
+        return RLMConvertNotFound(_backingList.find(context, object));
+    });
+}
+
+- (id)valueForKeyPath:(NSString *)keyPath {
+    if ([keyPath hasPrefix:@"@"]) {
+        // Delegate KVC collection operators to RLMResults
+        return translateErrors([&] {
+            auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingList.as_results()];
+            return [results valueForKeyPath:keyPath];
+        });
+    }
+    return [super valueForKeyPath:keyPath];
+}
+
+- (id)valueForKey:(NSString *)key {
+    // Ideally we'd use "@invalidated" for this so that "invalidated" would use
+    // normal array KVC semantics, but observing @things works very oddly (when
+    // it's part of a key path, it's triggered automatically when array index
+    // changes occur, and can't be sent explicitly, but works normally when it's
+    // the entire key path), and an RLMManagedArray *can't* have objects where
+    // invalidated is true, so we're not losing much.
+    return translateErrors([&]() -> id {
+        if ([key isEqualToString:RLMInvalidatedKey]) {
+            return @(!_backingList.is_valid());
+        }
+
+        _backingList.verify_attached();
+        return RLMCollectionValueForKey(_backingList, key, _realm, *_objectInfo);
+    });
+}
+
+- (void)setValue:(id)value forKey:(NSString *)key {
+    if ([key isEqualToString:@"self"]) {
+        RLMArrayValidateMatchingObjectType(self, value);
+        RLMAccessorContext context(_realm, *_objectInfo);
+        translateErrors([&] {
+            for (size_t i = 0, count = _backingList.size(); i < count; ++i) {
+                _backingList.set(context, i, value);
+            }
+        });
+        return;
+    }
+    else if (_type == RLMPropertyTypeObject) {
+        RLMArrayValidateMatchingObjectType(self, value);
+        translateErrors([&] { _backingList.verify_in_transaction(); });
+        RLMCollectionSetValueForKey(self, key, value);
+    }
+    else {
+        [self setValue:value forUndefinedKey:key];
+    }
+}
+
+- (size_t)columnForProperty:(NSString *)propertyName {
+    if (_backingList.get_type() == realm::PropertyType::Object) {
+        return _objectInfo->tableColumn(propertyName);
+    }
+    if (![propertyName isEqualToString:@"self"]) {
+        @throw RLMException(@"Arrays of '%@' can only be aggregated on \"self\"", RLMTypeToString(_type));
+    }
+    return 0;
+}
+
+- (id)minOfProperty:(NSString *)property {
+    size_t column = [self columnForProperty:property];
+    auto value = translateErrors(self, [&] { return _backingList.min(column); }, @"minOfProperty");
+    return value ? RLMMixedToObjc(*value) : nil;
+}
+
+- (id)maxOfProperty:(NSString *)property {
+    size_t column = [self columnForProperty:property];
+    auto value = translateErrors(self, [&] { return _backingList.max(column); }, @"maxOfProperty");
+    return value ? RLMMixedToObjc(*value) : nil;
+}
+
+- (id)sumOfProperty:(NSString *)property {
+    size_t column = [self columnForProperty:property];
+    return RLMMixedToObjc(translateErrors(self, [&] { return _backingList.sum(column); }, @"sumOfProperty"));
+}
+
+- (id)averageOfProperty:(NSString *)property {
+    size_t column = [self columnForProperty:property];
+    auto value = translateErrors(self, [&] { return _backingList.average(column); }, @"averageOfProperty");
+    return value ? @(*value) : nil;
+}
+
+- (void)deleteObjectsFromRealm {
+    if (_type != RLMPropertyTypeObject) {
+        @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.", RLMTypeToString(_type));
+    }
+    // delete all target rows from the realm
+    RLMTrackDeletions(_realm, ^{
+        translateErrors([&] { _backingList.delete_all(); });
+    });
+}
+
+- (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
+    return translateErrors([&] {
+        return [RLMResults resultsWithObjectInfo:*_objectInfo
+                                         results:_backingList.sort(RLMSortDescriptorsToKeypathArray(properties))];
+    });
+}
+
+- (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
+    if (_type != RLMPropertyTypeObject) {
+        @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
+    }
+    auto query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, _realm.schema, _realm.group);
+    auto results = translateErrors([&] { return _backingList.filter(std::move(query)); });
+    return [RLMResults resultsWithObjectInfo:*_objectInfo results:std::move(results)];
+}
+
+- (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
+    if (_type != RLMPropertyTypeObject) {
+        @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
+    }
+    realm::Query query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema,
+                                             _realm.schema, _realm.group);
+
+    return translateErrors([&] {
+        return RLMConvertNotFound(_backingList.find(std::move(query)));
+    });
+}
+
+- (NSArray *)objectsAtIndexes:(__unused NSIndexSet *)indexes {
+    // FIXME: this is called by KVO when array changes are made. It's not clear
+    // why, and returning nil seems to work fine.
+    return nil;
+}
+
+- (void)addObserver:(id)observer
+         forKeyPath:(NSString *)keyPath
+            options:(NSKeyValueObservingOptions)options
+            context:(void *)context {
+    RLMEnsureArrayObservationInfo(_observationInfo, keyPath, self, self);
+    [super addObserver:observer forKeyPath:keyPath options:options context:context];
+}
+
+- (realm::TableView)tableView {
+    return translateErrors([&] { return _backingList.get_query(); }).find_all();
+}
+
+- (RLMFastEnumerator *)fastEnumerator {
+    return translateErrors([&] {
+        return [[RLMFastEnumerator alloc] initWithList:_backingList collection:self
+                                                 realm:_realm classInfo:*_objectInfo];
+    });
+}
+
+// The compiler complains about the method's argument type not matching due to
+// it not having the generic type attached, but it doesn't seem to be possible
+// to actually include the generic type
+// http://www.openradar.me/radar?id=6135653276319744
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmismatched-parameter-types"
+- (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
+    [_realm verifyNotificationsAreSupported:true];
+    return RLMAddNotificationBlock(self, _backingList, block);
+}
+#pragma clang diagnostic pop
+
+#pragma mark - Thread Confined Protocol Conformance
+
+- (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
+    auto list_reference = _realm->_realm->obtain_thread_safe_reference(_backingList);
+    return std::make_unique<realm::ThreadSafeReference<realm::List>>(std::move(list_reference));
+}
+
+- (RLMManagedArrayHandoverMetadata *)objectiveCMetadata {
+    RLMManagedArrayHandoverMetadata *metadata = [[RLMManagedArrayHandoverMetadata alloc] init];
+    metadata.parentClassName = _ownerInfo->rlmObjectSchema.className;
+    metadata.key = _key;
+    return metadata;
+}
+
++ (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
+                                     metadata:(RLMManagedArrayHandoverMetadata *)metadata
+                                        realm:(RLMRealm *)realm {
+    REALM_ASSERT_DEBUG(dynamic_cast<realm::ThreadSafeReference<realm::List> *>(reference.get()));
+    auto list_reference = static_cast<realm::ThreadSafeReference<realm::List> *>(reference.get());
+
+    auto list = realm->_realm->resolve_thread_safe_reference(std::move(*list_reference));
+    if (!list.is_valid()) {
+        return nil;
+    }
+    RLMClassInfo *parentInfo = &realm->_info[metadata.parentClassName];
+    return [[RLMManagedArray alloc] initWithList:std::move(list)
+                                            realm:realm
+                                       parentInfo:parentInfo
+                                         property:parentInfo->rlmObjectSchema[metadata.key]];
+}
+
+@end