added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMManagedArray.mm
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2014 Realm Inc.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 ////////////////////////////////////////////////////////////////////////////
18
19 #import "RLMArray_Private.hpp"
20
21 #import "RLMAccessor.hpp"
22 #import "RLMObjectSchema_Private.hpp"
23 #import "RLMObjectStore.h"
24 #import "RLMObject_Private.hpp"
25 #import "RLMObservation.hpp"
26 #import "RLMProperty_Private.h"
27 #import "RLMQueryUtil.hpp"
28 #import "RLMRealm_Private.hpp"
29 #import "RLMSchema.h"
30 #import "RLMThreadSafeReference_Private.hpp"
31 #import "RLMUtil.hpp"
32
33 #import "list.hpp"
34 #import "results.hpp"
35 #import "shared_realm.hpp"
36
37 #import <realm/table_view.hpp>
38 #import <objc/runtime.h>
39
40 @interface RLMManagedArrayHandoverMetadata : NSObject
41 @property (nonatomic) NSString *parentClassName;
42 @property (nonatomic) NSString *key;
43 @end
44
45 @implementation RLMManagedArrayHandoverMetadata
46 @end
47
48 @interface RLMManagedArray () <RLMThreadConfined_Private>
49 @end
50
51 //
52 // RLMArray implementation
53 //
54 @implementation RLMManagedArray {
55 @public
56     realm::List _backingList;
57     RLMRealm *_realm;
58     RLMClassInfo *_objectInfo;
59     RLMClassInfo *_ownerInfo;
60     std::unique_ptr<RLMObservationInfo> _observationInfo;
61 }
62
63 - (RLMManagedArray *)initWithList:(realm::List)list
64                             realm:(__unsafe_unretained RLMRealm *const)realm
65                        parentInfo:(RLMClassInfo *)parentInfo
66                          property:(__unsafe_unretained RLMProperty *const)property {
67     if (property.type == RLMPropertyTypeObject)
68         self = [self initWithObjectClassName:property.objectClassName];
69     else
70         self = [self initWithObjectType:property.type optional:property.optional];
71     if (self) {
72         _realm = realm;
73         REALM_ASSERT(list.get_realm() == realm->_realm);
74         _backingList = std::move(list);
75         _ownerInfo = parentInfo;
76         if (property.type == RLMPropertyTypeObject)
77             _objectInfo = &parentInfo->linkTargetType(property.index);
78         else
79             _objectInfo = _ownerInfo;
80         _key = property.name;
81     }
82     return self;
83 }
84
85 - (RLMManagedArray *)initWithParent:(__unsafe_unretained RLMObjectBase *const)parentObject
86                            property:(__unsafe_unretained RLMProperty *const)property {
87     __unsafe_unretained RLMRealm *const realm = parentObject->_realm;
88     auto col = parentObject->_info->tableColumn(property);
89     auto& row = parentObject->_row;
90     return [self initWithList:realm::List(realm->_realm, *row.get_table(), col, row.get_index())
91                         realm:realm
92                    parentInfo:parentObject->_info
93                      property:property];
94 }
95
96 void RLMValidateArrayObservationKey(__unsafe_unretained NSString *const keyPath,
97                                     __unsafe_unretained RLMArray *const array) {
98     if (![keyPath isEqualToString:RLMInvalidatedKey]) {
99         @throw RLMException(@"[<%@ %p> addObserver:forKeyPath:options:context:] is not supported. Key path: %@",
100                             [array class], array, keyPath);
101     }
102 }
103
104 void RLMEnsureArrayObservationInfo(std::unique_ptr<RLMObservationInfo>& info,
105                                    __unsafe_unretained NSString *const keyPath,
106                                    __unsafe_unretained RLMArray *const array,
107                                    __unsafe_unretained id const observed) {
108     RLMValidateArrayObservationKey(keyPath, array);
109     if (!info && array.class == [RLMManagedArray class]) {
110         auto lv = static_cast<RLMManagedArray *>(array);
111         info = std::make_unique<RLMObservationInfo>(*lv->_ownerInfo,
112                                                     lv->_backingList.get_origin_row_index(),
113                                                     observed);
114     }
115 }
116
117 //
118 // validation helpers
119 //
120 [[gnu::noinline]]
121 [[noreturn]]
122 static void throwError(__unsafe_unretained RLMManagedArray *const ar, NSString *aggregateMethod) {
123     try {
124         throw;
125     }
126     catch (realm::InvalidTransactionException const&) {
127         @throw RLMException(@"Cannot modify managed RLMArray outside of a write transaction.");
128     }
129     catch (realm::IncorrectThreadException const&) {
130         @throw RLMException(@"Realm accessed from incorrect thread.");
131     }
132     catch (realm::List::InvalidatedException const&) {
133         @throw RLMException(@"RLMArray has been invalidated or the containing object has been deleted.");
134     }
135     catch (realm::List::OutOfBoundsIndexException const& e) {
136         @throw RLMException(@"Index %zu is out of bounds (must be less than %zu).",
137                             e.requested, e.valid_count);
138     }
139     catch (realm::Results::UnsupportedColumnTypeException const& e) {
140         if (ar->_backingList.get_type() == realm::PropertyType::Object) {
141             @throw RLMException(@"%@: is not supported for %s%s property '%s'.",
142                                 aggregateMethod,
143                                 string_for_property_type(e.property_type),
144                                 is_nullable(e.property_type) ? "?" : "",
145                                 e.column_name.data());
146         }
147         @throw RLMException(@"%@: is not supported for %s%s array '%@.%@'.",
148                             aggregateMethod,
149                             string_for_property_type(e.property_type),
150                             is_nullable(e.property_type) ? "?" : "",
151                             ar->_ownerInfo->rlmObjectSchema.className, ar->_key);
152     }
153     catch (std::logic_error const& e) {
154         @throw RLMException(e);
155     }
156 }
157
158 template<typename Function>
159 static auto translateErrors(__unsafe_unretained RLMManagedArray *const ar,
160                             Function&& f, NSString *aggregateMethod=nil) {
161     try {
162         return f();
163     }
164     catch (...) {
165         throwError(ar, aggregateMethod);
166     }
167 }
168
169 template<typename Function>
170 static auto translateErrors(Function&& f) {
171     try {
172         return f();
173     }
174     catch (...) {
175         throwError(nil, nil);
176     }
177 }
178
179 template<typename IndexSetFactory>
180 static void changeArray(__unsafe_unretained RLMManagedArray *const ar,
181                         NSKeyValueChange kind, dispatch_block_t f, IndexSetFactory&& is) {
182     translateErrors([&] { ar->_backingList.verify_in_transaction(); });
183     RLMObservationInfo *info = RLMGetObservationInfo(ar->_observationInfo.get(),
184                                                      ar->_backingList.get_origin_row_index(),
185                                                      *ar->_ownerInfo);
186     if (info) {
187         NSIndexSet *indexes = is();
188         info->willChange(ar->_key, kind, indexes);
189         try {
190             f();
191         }
192         catch (...) {
193             info->didChange(ar->_key, kind, indexes);
194             throwError(ar, nil);
195         }
196         info->didChange(ar->_key, kind, indexes);
197     }
198     else {
199         translateErrors([&] { f(); });
200     }
201 }
202
203 static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSUInteger index, dispatch_block_t f) {
204     changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndex:index]; });
205 }
206
207 static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSRange range, dispatch_block_t f) {
208     changeArray(ar, kind, f, [=] { return [NSIndexSet indexSetWithIndexesInRange:range]; });
209 }
210
211 static void changeArray(__unsafe_unretained RLMManagedArray *const ar, NSKeyValueChange kind, NSIndexSet *is, dispatch_block_t f) {
212     changeArray(ar, kind, f, [=] { return is; });
213 }
214
215 //
216 // public method implementations
217 //
218 - (RLMRealm *)realm {
219     return _realm;
220 }
221
222 - (NSUInteger)count {
223     return translateErrors([&] { return _backingList.size(); });
224 }
225
226 - (BOOL)isInvalidated {
227     return translateErrors([&] { return !_backingList.is_valid(); });
228 }
229
230 - (RLMClassInfo *)objectInfo {
231     return _objectInfo;
232 }
233
234
235 - (bool)isBackedByList:(realm::List const&)list {
236     return _backingList == list;
237 }
238
239 - (BOOL)isEqual:(id)object {
240     return [object respondsToSelector:@selector(isBackedByList:)] && [object isBackedByList:_backingList];
241 }
242
243 - (NSUInteger)hash {
244     return std::hash<realm::List>()(_backingList);
245 }
246
247 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
248                                   objects:(__unused __unsafe_unretained id [])buffer
249                                     count:(NSUInteger)len {
250     return RLMFastEnumerate(state, len, self);
251 }
252
253 - (id)objectAtIndex:(NSUInteger)index {
254     return translateErrors([&] {
255         RLMAccessorContext context(_realm, *_objectInfo);
256         return _backingList.get(context, index);
257     });
258 }
259
260 static void RLMInsertObject(RLMManagedArray *ar, id object, NSUInteger index) {
261     RLMArrayValidateMatchingObjectType(ar, object);
262     if (index == NSUIntegerMax) {
263         index = translateErrors([&] { return ar->_backingList.size(); });
264     }
265
266     changeArray(ar, NSKeyValueChangeInsertion, index, ^{
267         RLMAccessorContext context(ar->_realm, *ar->_objectInfo);
268         ar->_backingList.insert(context, index, object);
269     });
270 }
271
272 - (void)addObject:(id)object {
273     RLMInsertObject(self, object, NSUIntegerMax);
274 }
275
276 - (void)insertObject:(id)object atIndex:(NSUInteger)index {
277     RLMInsertObject(self, object, index);
278 }
279
280 - (void)insertObjects:(id<NSFastEnumeration>)objects atIndexes:(NSIndexSet *)indexes {
281     changeArray(self, NSKeyValueChangeInsertion, indexes, ^{
282         NSUInteger index = [indexes firstIndex];
283         RLMAccessorContext context(_realm, *_objectInfo);
284         for (id obj in objects) {
285             RLMArrayValidateMatchingObjectType(self, obj);
286             _backingList.insert(context, index, obj);
287             index = [indexes indexGreaterThanIndex:index];
288         }
289     });
290 }
291
292
293 - (void)removeObjectAtIndex:(NSUInteger)index {
294     changeArray(self, NSKeyValueChangeRemoval, index, ^{
295         _backingList.remove(index);
296     });
297 }
298
299 - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes {
300     changeArray(self, NSKeyValueChangeRemoval, indexes, ^{
301         [indexes enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *) {
302             _backingList.remove(idx);
303         }];
304     });
305 }
306
307 - (void)addObjectsFromArray:(NSArray *)array {
308     changeArray(self, NSKeyValueChangeInsertion, NSMakeRange(self.count, array.count), ^{
309         RLMAccessorContext context(_realm, *_objectInfo);
310         for (id obj in array) {
311             RLMArrayValidateMatchingObjectType(self, obj);
312             _backingList.add(context, obj);
313         }
314     });
315 }
316
317 - (void)removeAllObjects {
318     changeArray(self, NSKeyValueChangeRemoval, NSMakeRange(0, self.count), ^{
319         _backingList.remove_all();
320     });
321 }
322
323 - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)object {
324     RLMArrayValidateMatchingObjectType(self, object);
325     changeArray(self, NSKeyValueChangeReplacement, index, ^{
326         RLMAccessorContext context(_realm, *_objectInfo);
327         _backingList.set(context, index, object);
328     });
329 }
330
331 - (void)moveObjectAtIndex:(NSUInteger)sourceIndex toIndex:(NSUInteger)destinationIndex {
332     auto start = std::min(sourceIndex, destinationIndex);
333     auto len = std::max(sourceIndex, destinationIndex) - start + 1;
334     changeArray(self, NSKeyValueChangeReplacement, {start, len}, ^{
335         _backingList.move(sourceIndex, destinationIndex);
336     });
337 }
338
339 - (void)exchangeObjectAtIndex:(NSUInteger)index1 withObjectAtIndex:(NSUInteger)index2 {
340     changeArray(self, NSKeyValueChangeReplacement, ^{
341         _backingList.swap(index1, index2);
342     }, [=] {
343         NSMutableIndexSet *set = [[NSMutableIndexSet alloc] initWithIndex:index1];
344         [set addIndex:index2];
345         return set;
346     });
347 }
348
349 - (NSUInteger)indexOfObject:(id)object {
350     RLMArrayValidateMatchingObjectType(self, object);
351     return translateErrors([&] {
352         RLMAccessorContext context(_realm, *_objectInfo);
353         return RLMConvertNotFound(_backingList.find(context, object));
354     });
355 }
356
357 - (id)valueForKeyPath:(NSString *)keyPath {
358     if ([keyPath hasPrefix:@"@"]) {
359         // Delegate KVC collection operators to RLMResults
360         return translateErrors([&] {
361             auto results = [RLMResults resultsWithObjectInfo:*_objectInfo results:_backingList.as_results()];
362             return [results valueForKeyPath:keyPath];
363         });
364     }
365     return [super valueForKeyPath:keyPath];
366 }
367
368 - (id)valueForKey:(NSString *)key {
369     // Ideally we'd use "@invalidated" for this so that "invalidated" would use
370     // normal array KVC semantics, but observing @things works very oddly (when
371     // it's part of a key path, it's triggered automatically when array index
372     // changes occur, and can't be sent explicitly, but works normally when it's
373     // the entire key path), and an RLMManagedArray *can't* have objects where
374     // invalidated is true, so we're not losing much.
375     return translateErrors([&]() -> id {
376         if ([key isEqualToString:RLMInvalidatedKey]) {
377             return @(!_backingList.is_valid());
378         }
379
380         _backingList.verify_attached();
381         return RLMCollectionValueForKey(_backingList, key, _realm, *_objectInfo);
382     });
383 }
384
385 - (void)setValue:(id)value forKey:(NSString *)key {
386     if ([key isEqualToString:@"self"]) {
387         RLMArrayValidateMatchingObjectType(self, value);
388         RLMAccessorContext context(_realm, *_objectInfo);
389         translateErrors([&] {
390             for (size_t i = 0, count = _backingList.size(); i < count; ++i) {
391                 _backingList.set(context, i, value);
392             }
393         });
394         return;
395     }
396     else if (_type == RLMPropertyTypeObject) {
397         RLMArrayValidateMatchingObjectType(self, value);
398         translateErrors([&] { _backingList.verify_in_transaction(); });
399         RLMCollectionSetValueForKey(self, key, value);
400     }
401     else {
402         [self setValue:value forUndefinedKey:key];
403     }
404 }
405
406 - (size_t)columnForProperty:(NSString *)propertyName {
407     if (_backingList.get_type() == realm::PropertyType::Object) {
408         return _objectInfo->tableColumn(propertyName);
409     }
410     if (![propertyName isEqualToString:@"self"]) {
411         @throw RLMException(@"Arrays of '%@' can only be aggregated on \"self\"", RLMTypeToString(_type));
412     }
413     return 0;
414 }
415
416 - (id)minOfProperty:(NSString *)property {
417     size_t column = [self columnForProperty:property];
418     auto value = translateErrors(self, [&] { return _backingList.min(column); }, @"minOfProperty");
419     return value ? RLMMixedToObjc(*value) : nil;
420 }
421
422 - (id)maxOfProperty:(NSString *)property {
423     size_t column = [self columnForProperty:property];
424     auto value = translateErrors(self, [&] { return _backingList.max(column); }, @"maxOfProperty");
425     return value ? RLMMixedToObjc(*value) : nil;
426 }
427
428 - (id)sumOfProperty:(NSString *)property {
429     size_t column = [self columnForProperty:property];
430     return RLMMixedToObjc(translateErrors(self, [&] { return _backingList.sum(column); }, @"sumOfProperty"));
431 }
432
433 - (id)averageOfProperty:(NSString *)property {
434     size_t column = [self columnForProperty:property];
435     auto value = translateErrors(self, [&] { return _backingList.average(column); }, @"averageOfProperty");
436     return value ? @(*value) : nil;
437 }
438
439 - (void)deleteObjectsFromRealm {
440     if (_type != RLMPropertyTypeObject) {
441         @throw RLMException(@"Cannot delete objects from RLMArray<%@>: only RLMObjects can be deleted.", RLMTypeToString(_type));
442     }
443     // delete all target rows from the realm
444     RLMTrackDeletions(_realm, ^{
445         translateErrors([&] { _backingList.delete_all(); });
446     });
447 }
448
449 - (RLMResults *)sortedResultsUsingDescriptors:(NSArray<RLMSortDescriptor *> *)properties {
450     return translateErrors([&] {
451         return [RLMResults resultsWithObjectInfo:*_objectInfo
452                                          results:_backingList.sort(RLMSortDescriptorsToKeypathArray(properties))];
453     });
454 }
455
456 - (RLMResults *)objectsWithPredicate:(NSPredicate *)predicate {
457     if (_type != RLMPropertyTypeObject) {
458         @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
459     }
460     auto query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema, _realm.schema, _realm.group);
461     auto results = translateErrors([&] { return _backingList.filter(std::move(query)); });
462     return [RLMResults resultsWithObjectInfo:*_objectInfo results:std::move(results)];
463 }
464
465 - (NSUInteger)indexOfObjectWithPredicate:(NSPredicate *)predicate {
466     if (_type != RLMPropertyTypeObject) {
467         @throw RLMException(@"Querying is currently only implemented for arrays of Realm Objects");
468     }
469     realm::Query query = RLMPredicateToQuery(predicate, _objectInfo->rlmObjectSchema,
470                                              _realm.schema, _realm.group);
471
472     return translateErrors([&] {
473         return RLMConvertNotFound(_backingList.find(std::move(query)));
474     });
475 }
476
477 - (NSArray *)objectsAtIndexes:(__unused NSIndexSet *)indexes {
478     // FIXME: this is called by KVO when array changes are made. It's not clear
479     // why, and returning nil seems to work fine.
480     return nil;
481 }
482
483 - (void)addObserver:(id)observer
484          forKeyPath:(NSString *)keyPath
485             options:(NSKeyValueObservingOptions)options
486             context:(void *)context {
487     RLMEnsureArrayObservationInfo(_observationInfo, keyPath, self, self);
488     [super addObserver:observer forKeyPath:keyPath options:options context:context];
489 }
490
491 - (realm::TableView)tableView {
492     return translateErrors([&] { return _backingList.get_query(); }).find_all();
493 }
494
495 - (RLMFastEnumerator *)fastEnumerator {
496     return translateErrors([&] {
497         return [[RLMFastEnumerator alloc] initWithList:_backingList collection:self
498                                                  realm:_realm classInfo:*_objectInfo];
499     });
500 }
501
502 // The compiler complains about the method's argument type not matching due to
503 // it not having the generic type attached, but it doesn't seem to be possible
504 // to actually include the generic type
505 // http://www.openradar.me/radar?id=6135653276319744
506 #pragma clang diagnostic push
507 #pragma clang diagnostic ignored "-Wmismatched-parameter-types"
508 - (RLMNotificationToken *)addNotificationBlock:(void (^)(RLMArray *, RLMCollectionChange *, NSError *))block {
509     [_realm verifyNotificationsAreSupported:true];
510     return RLMAddNotificationBlock(self, _backingList, block);
511 }
512 #pragma clang diagnostic pop
513
514 #pragma mark - Thread Confined Protocol Conformance
515
516 - (std::unique_ptr<realm::ThreadSafeReferenceBase>)makeThreadSafeReference {
517     auto list_reference = _realm->_realm->obtain_thread_safe_reference(_backingList);
518     return std::make_unique<realm::ThreadSafeReference<realm::List>>(std::move(list_reference));
519 }
520
521 - (RLMManagedArrayHandoverMetadata *)objectiveCMetadata {
522     RLMManagedArrayHandoverMetadata *metadata = [[RLMManagedArrayHandoverMetadata alloc] init];
523     metadata.parentClassName = _ownerInfo->rlmObjectSchema.className;
524     metadata.key = _key;
525     return metadata;
526 }
527
528 + (instancetype)objectWithThreadSafeReference:(std::unique_ptr<realm::ThreadSafeReferenceBase>)reference
529                                      metadata:(RLMManagedArrayHandoverMetadata *)metadata
530                                         realm:(RLMRealm *)realm {
531     REALM_ASSERT_DEBUG(dynamic_cast<realm::ThreadSafeReference<realm::List> *>(reference.get()));
532     auto list_reference = static_cast<realm::ThreadSafeReference<realm::List> *>(reference.get());
533
534     auto list = realm->_realm->resolve_thread_safe_reference(std::move(*list_reference));
535     if (!list.is_valid()) {
536         return nil;
537     }
538     RLMClassInfo *parentInfo = &realm->_info[metadata.parentClassName];
539     return [[RLMManagedArray alloc] initWithList:std::move(list)
540                                             realm:realm
541                                        parentInfo:parentInfo
542                                          property:parentInfo->rlmObjectSchema[metadata.key]];
543 }
544
545 @end