An amazing project that generates micro reports from tournament results
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

545 lines
20 KiB

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