added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMObjectSchema.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 "RLMObjectSchema_Private.hpp"
20
21 #import "RLMArray.h"
22 #import "RLMListBase.h"
23 #import "RLMObject_Private.h"
24 #import "RLMProperty_Private.hpp"
25 #import "RLMRealm_Dynamic.h"
26 #import "RLMRealm_Private.hpp"
27 #import "RLMSchema_Private.h"
28 #import "RLMSwiftSupport.h"
29 #import "RLMUtil.hpp"
30
31 #import "object_store.hpp"
32
33 using namespace realm;
34
35 // private properties
36 @interface RLMObjectSchema ()
37 @property (nonatomic, readwrite) NSDictionary<id, RLMProperty *> *allPropertiesByName;
38 @property (nonatomic, readwrite) NSString *className;
39 @end
40
41 @implementation RLMObjectSchema {
42     NSArray *_swiftGenericProperties;
43 }
44
45 - (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties {
46     self = [super init];
47     self.className = objectClassName;
48     self.properties = properties;
49     self.objectClass = objectClass;
50     self.accessorClass = objectClass;
51     self.unmanagedClass = objectClass;
52     return self;
53 }
54
55 // return properties by name
56 - (RLMProperty *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)key {
57     return _allPropertiesByName[key];
58 }
59
60 // create property map when setting property array
61 - (void)setProperties:(NSArray *)properties {
62     _properties = properties;
63     [self _propertiesDidChange];
64 }
65
66 - (void)setComputedProperties:(NSArray *)computedProperties {
67     _computedProperties = computedProperties;
68     [self _propertiesDidChange];
69 }
70
71 - (void)_propertiesDidChange {
72     NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:_properties.count + _computedProperties.count];
73     NSUInteger index = 0;
74     for (RLMProperty *prop in _properties) {
75         prop.index = index++;
76         map[prop.name] = prop;
77         if (prop.isPrimary) {
78             self.primaryKeyProperty = prop;
79         }
80     }
81     for (RLMProperty *prop in _computedProperties) {
82         map[prop.name] = prop;
83     }
84     _allPropertiesByName = map;
85 }
86
87
88 - (void)setPrimaryKeyProperty:(RLMProperty *)primaryKeyProperty {
89     _primaryKeyProperty.isPrimary = NO;
90     primaryKeyProperty.isPrimary = YES;
91     _primaryKeyProperty = primaryKeyProperty;
92 }
93
94 + (instancetype)schemaForObjectClass:(Class)objectClass {
95     RLMObjectSchema *schema = [RLMObjectSchema new];
96
97     // determine classname from objectclass as className method has not yet been updated
98     NSString *className = NSStringFromClass(objectClass);
99     bool hasSwiftName = [RLMSwiftSupport isSwiftClassName:className];
100     if (hasSwiftName) {
101         className = [RLMSwiftSupport demangleClassName:className];
102     }
103
104     static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
105     bool isSwift = hasSwiftName || [objectClass isSubclassOfClass:s_swiftObjectClass];
106
107     schema.className = className;
108     schema.objectClass = objectClass;
109     schema.accessorClass = objectClass;
110     schema.isSwiftClass = isSwift;
111
112     // create array of RLMProperties, inserting properties of superclasses first
113     Class cls = objectClass;
114     Class superClass = class_getSuperclass(cls);
115     NSArray *allProperties = @[];
116     while (superClass && superClass != RLMObjectBase.class) {
117         allProperties = [[RLMObjectSchema propertiesForClass:cls isSwift:isSwift]
118                          arrayByAddingObjectsFromArray:allProperties];
119         cls = superClass;
120         superClass = class_getSuperclass(superClass);
121     }
122     NSArray *persistedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
123         return !RLMPropertyTypeIsComputed(property.type);
124     }]];
125     schema.properties = persistedProperties;
126
127     NSArray *computedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
128         return RLMPropertyTypeIsComputed(property.type);
129     }]];
130     schema.computedProperties = computedProperties;
131
132     // verify that we didn't add any properties twice due to inheritance
133     if (allProperties.count != [NSSet setWithArray:[allProperties valueForKey:@"name"]].count) {
134         NSCountedSet *countedPropertyNames = [NSCountedSet setWithArray:[allProperties valueForKey:@"name"]];
135         NSArray *duplicatePropertyNames = [countedPropertyNames filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *) {
136             return [countedPropertyNames countForObject:object] > 1;
137         }]].allObjects;
138
139         if (duplicatePropertyNames.count == 1) {
140             @throw RLMException(@"Property '%@' is declared multiple times in the class hierarchy of '%@'", duplicatePropertyNames.firstObject, className);
141         } else {
142             @throw RLMException(@"Object '%@' has properties that are declared multiple times in its class hierarchy: '%@'", className, [duplicatePropertyNames componentsJoinedByString:@"', '"]);
143         }
144     }
145
146     if (NSString *primaryKey = [objectClass primaryKey]) {
147         for (RLMProperty *prop in schema.properties) {
148             if ([primaryKey isEqualToString:prop.name]) {
149                 prop.indexed = YES;
150                 schema.primaryKeyProperty = prop;
151                 break;
152             }
153         }
154
155         if (!schema.primaryKeyProperty) {
156             @throw RLMException(@"Primary key property '%@' does not exist on object '%@'", primaryKey, className);
157         }
158         if (schema.primaryKeyProperty.type != RLMPropertyTypeInt && schema.primaryKeyProperty.type != RLMPropertyTypeString) {
159             @throw RLMException(@"Property '%@' cannot be made the primary key of '%@' because it is not a 'string' or 'int' property.",
160                                 primaryKey, className);
161         }
162     }
163
164     for (RLMProperty *prop in schema.properties) {
165         if (prop.optional && prop.array && (prop.type == RLMPropertyTypeObject || prop.type == RLMPropertyTypeLinkingObjects)) {
166             // FIXME: message is awkward
167             @throw RLMException(@"Property '%@.%@' cannot be made optional because optional '%@' properties are not supported.",
168                                 className, prop.name, RLMTypeToString(prop.type));
169         }
170     }
171
172     return schema;
173 }
174
175 + (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass {
176     Class objectUtil = [objectClass objectUtilClass:isSwiftClass];
177     NSArray *ignoredProperties = [objectUtil ignoredPropertiesForClass:objectClass];
178     NSDictionary *linkingObjectsProperties = [objectUtil linkingObjectsPropertiesForClass:objectClass];
179
180     // For Swift classes we need an instance of the object when parsing properties
181     id swiftObjectInstance = isSwiftClass ? [[objectClass alloc] init] : nil;
182
183     unsigned int count;
184     std::unique_ptr<objc_property_t[], decltype(&free)> props(class_copyPropertyList(objectClass, &count), &free);
185     NSMutableArray<RLMProperty *> *propArray = [NSMutableArray arrayWithCapacity:count];
186     NSSet *indexed = [[NSSet alloc] initWithArray:[objectUtil indexedPropertiesForClass:objectClass]];
187     for (unsigned int i = 0; i < count; i++) {
188         NSString *propertyName = @(property_getName(props[i]));
189         if ([ignoredProperties containsObject:propertyName]) {
190             continue;
191         }
192
193         RLMProperty *prop = nil;
194         if (isSwiftClass) {
195             prop = [[RLMProperty alloc] initSwiftPropertyWithName:propertyName
196                                                           indexed:[indexed containsObject:propertyName]
197                                            linkPropertyDescriptor:linkingObjectsProperties[propertyName]
198                                                          property:props[i]
199                                                          instance:swiftObjectInstance];
200         }
201         else {
202             prop = [[RLMProperty alloc] initWithName:propertyName
203                                              indexed:[indexed containsObject:propertyName]
204                               linkPropertyDescriptor:linkingObjectsProperties[propertyName]
205                                             property:props[i]];
206         }
207
208         if (prop) {
209             [propArray addObject:prop];
210         }
211     }
212
213     if (isSwiftClass) {
214         [self addSwiftProperties:propArray objectUtil:objectUtil instance:swiftObjectInstance indexed:indexed];
215     }
216
217     if (auto requiredProperties = [objectUtil requiredPropertiesForClass:objectClass]) {
218         for (RLMProperty *property in propArray) {
219             bool required = [requiredProperties containsObject:property.name];
220             if (required && property.type == RLMPropertyTypeObject && !property.array) {
221                 @throw RLMException(@"Object properties cannot be made required, "
222                                     "but '+[%@ requiredProperties]' included '%@'", objectClass, property.name);
223             }
224             property.optional &= !required;
225         }
226     }
227
228     for (RLMProperty *property in propArray) {
229         if (!property.optional && property.type == RLMPropertyTypeObject && !property.array) {
230             @throw RLMException(@"The `%@.%@` property must be marked as being optional.",
231                                 [objectClass className], property.name);
232         }
233     }
234
235     return propArray;
236 }
237
238 + (void)addSwiftProperties:(NSMutableArray<RLMProperty *> *)propArray
239                 objectUtil:(Class)objectUtil
240                   instance:(id)instance
241                    indexed:(NSSet<NSString *> *)indexed {
242     // The property list reported to the obj-c runtime for Swift objects is
243     // incomplete and doesn't include Swift generics like List<> and
244     // RealmOptional<>, and is missing information for some properties that
245     // are reported, such as the difference between `String` and `String?`. To
246     // deal with this, we also get the properties from Swift reflection, and
247     // merge the results.
248
249     NSArray<RLMSwiftPropertyMetadata *> *props = [objectUtil getSwiftProperties:instance];
250     if (!props) {
251         // A Swift subclass of RLMObject, which operates under obj-c rules
252         return;
253     }
254
255     // Track the index that we expect the next property to go in, for inserting
256     // generic properties into the correct place
257     NSUInteger nextIndex = 0;
258     for (RLMSwiftPropertyMetadata *md in props) {
259         // In theory existing should only ever be nextIndex or NSNotFound, and
260         // this search is just a waste of time.
261         // FIXME: verify if this is actually true
262         NSUInteger existing = [propArray indexOfObjectPassingTest:^(RLMProperty *obj, NSUInteger, BOOL *) {
263             return [obj.name isEqualToString:md.propertyName];
264         }];
265
266         switch (md.kind) {
267             case RLMSwiftPropertyKindList: // List<>
268                 [propArray insertObject:[[RLMProperty alloc] initSwiftListPropertyWithName:md.propertyName
269                                                                                   instance:instance]
270                                 atIndex:nextIndex];
271                 break;
272             case RLMSwiftPropertyKindLinkingObjects: { // LinkingObjects<>
273                 Ivar ivar = class_getInstanceVariable([instance class], md.propertyName.UTF8String);
274                 [propArray insertObject:[[RLMProperty alloc] initSwiftLinkingObjectsPropertyWithName:md.propertyName
275                                                                                                 ivar:ivar
276                                                                                      objectClassName:md.className
277                                                                               linkOriginPropertyName:md.linkedPropertyName]
278                                 atIndex:nextIndex];
279                 break;
280             }
281             case RLMSwiftPropertyKindOptional: {
282                 if (existing != NSNotFound) {
283                     // String?, Data?, Date? with a non-nil default value
284                     // We already know about this property from obj-c and we
285                     // defaulted to optional, so nothing to do
286                     break;
287                 }
288
289                 // RealmOptional<>
290                 Ivar ivar = class_getInstanceVariable([instance class], md.propertyName.UTF8String);
291                 BOOL isIndexed = [indexed containsObject:md.propertyName];
292                 [propArray insertObject:[[RLMProperty alloc] initSwiftOptionalPropertyWithName:md.propertyName
293                                                                                        indexed:isIndexed
294                                                                                           ivar:ivar
295                                                                                   propertyType:md.propertyType]
296                                 atIndex:nextIndex];
297                 break;
298             }
299
300             case RLMSwiftPropertyKindOther:
301             case RLMSwiftPropertyKindNilLiteralOptional:
302                 // This might be a property which wasn't reported to obj-c and
303                 // isn't one of our supported generic types, in which case we
304                 // ignore it
305                 if (existing == NSNotFound) {
306                     --nextIndex;
307                 }
308                 // or it might be a String?, Data?, Date? or object field with
309                 // a nil default value
310                 else if (md.kind == RLMSwiftPropertyKindNilLiteralOptional) {
311                     propArray[existing].optional = true;
312                 }
313                 // or it may be some non-optional property which may have been
314                 // previously marked as optional due to that being the default
315                 // in obj-c
316                 else {
317                     propArray[existing].optional = false;
318                 }
319                 break;
320         }
321
322         ++nextIndex;
323     }
324 }
325
326 - (id)copyWithZone:(NSZone *)zone {
327     RLMObjectSchema *schema = [[RLMObjectSchema allocWithZone:zone] init];
328     schema->_objectClass = _objectClass;
329     schema->_className = _className;
330     schema->_objectClass = _objectClass;
331     schema->_accessorClass = _objectClass;
332     schema->_unmanagedClass = _unmanagedClass;
333     schema->_isSwiftClass = _isSwiftClass;
334
335     // call property setter to reset map and primary key
336     schema.properties = [[NSArray allocWithZone:zone] initWithArray:_properties copyItems:YES];
337     schema.computedProperties = [[NSArray allocWithZone:zone] initWithArray:_computedProperties copyItems:YES];
338
339     return schema;
340 }
341
342 - (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema {
343     if (objectSchema.properties.count != _properties.count) {
344         return NO;
345     }
346
347     if (![_properties isEqualToArray:objectSchema.properties]) {
348         return NO;
349     }
350     if (![_computedProperties isEqualToArray:objectSchema.computedProperties]) {
351         return NO;
352     }
353
354     return YES;
355 }
356
357 - (NSString *)description {
358     NSMutableString *propertiesString = [NSMutableString string];
359     for (RLMProperty *property in self.properties) {
360         [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
361     }
362     for (RLMProperty *property in self.computedProperties) {
363         [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
364     }
365     return [NSString stringWithFormat:@"%@ {\n%@}", self.className, propertiesString];
366 }
367
368 - (NSString *)objectName {
369     return [self.objectClass _realmObjectName] ?: _className;
370 }
371
372 - (realm::ObjectSchema)objectStoreCopy {
373     ObjectSchema objectSchema;
374     objectSchema.name = self.objectName.UTF8String;
375     objectSchema.primary_key = _primaryKeyProperty ? _primaryKeyProperty.name.UTF8String : "";
376     for (RLMProperty *prop in _properties) {
377         Property p = [prop objectStoreCopy];
378         p.is_primary = (prop == _primaryKeyProperty);
379         objectSchema.persisted_properties.push_back(std::move(p));
380     }
381     for (RLMProperty *prop in _computedProperties) {
382         objectSchema.computed_properties.push_back([prop objectStoreCopy]);
383     }
384     return objectSchema;
385 }
386
387 + (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema const&)objectSchema {
388     RLMObjectSchema *schema = [RLMObjectSchema new];
389     schema.className = @(objectSchema.name.c_str());
390
391     // create array of RLMProperties
392     NSMutableArray *properties = [NSMutableArray arrayWithCapacity:objectSchema.persisted_properties.size()];
393     for (const Property &prop : objectSchema.persisted_properties) {
394         RLMProperty *property = [RLMProperty propertyForObjectStoreProperty:prop];
395         property.isPrimary = (prop.name == objectSchema.primary_key);
396         [properties addObject:property];
397     }
398     schema.properties = properties;
399
400     NSMutableArray *computedProperties = [NSMutableArray arrayWithCapacity:objectSchema.computed_properties.size()];
401     for (const Property &prop : objectSchema.computed_properties) {
402         [computedProperties addObject:[RLMProperty propertyForObjectStoreProperty:prop]];
403     }
404     schema.computedProperties = computedProperties;
405
406     // get primary key from realm metadata
407     if (objectSchema.primary_key.length()) {
408         NSString *primaryKeyString = [NSString stringWithUTF8String:objectSchema.primary_key.c_str()];
409         schema.primaryKeyProperty = schema[primaryKeyString];
410         if (!schema.primaryKeyProperty) {
411             @throw RLMException(@"No property matching primary key '%@'", primaryKeyString);
412         }
413     }
414
415     // for dynamic schema use vanilla RLMDynamicObject accessor classes
416     schema.objectClass = RLMObject.class;
417     schema.accessorClass = RLMDynamicObject.class;
418     schema.unmanagedClass = RLMObject.class;
419
420     return schema;
421 }
422
423 - (NSArray *)swiftGenericProperties {
424     if (_swiftGenericProperties) {
425         return _swiftGenericProperties;
426     }
427
428     // This check isn't semantically required, but avoiding accessing the local
429     // static helps perf in the obj-c case
430     if (!_isSwiftClass) {
431         return _swiftGenericProperties = @[];
432     }
433
434     // Check if it's a swift class using the obj-c API
435     static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
436     if (![_accessorClass isSubclassOfClass:s_swiftObjectClass]) {
437         return _swiftGenericProperties = @[];
438     }
439
440     NSMutableArray *genericProperties = [NSMutableArray new];
441     for (RLMProperty *prop in _properties) {
442         if (prop->_swiftIvar) {
443             [genericProperties addObject:prop];
444         }
445     }
446     // Currently all computed properties are Swift generics
447     [genericProperties addObjectsFromArray:_computedProperties];
448
449     return _swiftGenericProperties = genericProperties;
450 }
451
452 @end