added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMObjectSchema.mm
diff --git a/iOS/Pods/Realm/Realm/RLMObjectSchema.mm b/iOS/Pods/Realm/Realm/RLMObjectSchema.mm
new file mode 100644 (file)
index 0000000..48516d6
--- /dev/null
@@ -0,0 +1,452 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// 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 "RLMObjectSchema_Private.hpp"
+
+#import "RLMArray.h"
+#import "RLMListBase.h"
+#import "RLMObject_Private.h"
+#import "RLMProperty_Private.hpp"
+#import "RLMRealm_Dynamic.h"
+#import "RLMRealm_Private.hpp"
+#import "RLMSchema_Private.h"
+#import "RLMSwiftSupport.h"
+#import "RLMUtil.hpp"
+
+#import "object_store.hpp"
+
+using namespace realm;
+
+// private properties
+@interface RLMObjectSchema ()
+@property (nonatomic, readwrite) NSDictionary<id, RLMProperty *> *allPropertiesByName;
+@property (nonatomic, readwrite) NSString *className;
+@end
+
+@implementation RLMObjectSchema {
+    NSArray *_swiftGenericProperties;
+}
+
+- (instancetype)initWithClassName:(NSString *)objectClassName objectClass:(Class)objectClass properties:(NSArray *)properties {
+    self = [super init];
+    self.className = objectClassName;
+    self.properties = properties;
+    self.objectClass = objectClass;
+    self.accessorClass = objectClass;
+    self.unmanagedClass = objectClass;
+    return self;
+}
+
+// return properties by name
+- (RLMProperty *)objectForKeyedSubscript:(__unsafe_unretained NSString *const)key {
+    return _allPropertiesByName[key];
+}
+
+// create property map when setting property array
+- (void)setProperties:(NSArray *)properties {
+    _properties = properties;
+    [self _propertiesDidChange];
+}
+
+- (void)setComputedProperties:(NSArray *)computedProperties {
+    _computedProperties = computedProperties;
+    [self _propertiesDidChange];
+}
+
+- (void)_propertiesDidChange {
+    NSMutableDictionary *map = [NSMutableDictionary dictionaryWithCapacity:_properties.count + _computedProperties.count];
+    NSUInteger index = 0;
+    for (RLMProperty *prop in _properties) {
+        prop.index = index++;
+        map[prop.name] = prop;
+        if (prop.isPrimary) {
+            self.primaryKeyProperty = prop;
+        }
+    }
+    for (RLMProperty *prop in _computedProperties) {
+        map[prop.name] = prop;
+    }
+    _allPropertiesByName = map;
+}
+
+
+- (void)setPrimaryKeyProperty:(RLMProperty *)primaryKeyProperty {
+    _primaryKeyProperty.isPrimary = NO;
+    primaryKeyProperty.isPrimary = YES;
+    _primaryKeyProperty = primaryKeyProperty;
+}
+
++ (instancetype)schemaForObjectClass:(Class)objectClass {
+    RLMObjectSchema *schema = [RLMObjectSchema new];
+
+    // determine classname from objectclass as className method has not yet been updated
+    NSString *className = NSStringFromClass(objectClass);
+    bool hasSwiftName = [RLMSwiftSupport isSwiftClassName:className];
+    if (hasSwiftName) {
+        className = [RLMSwiftSupport demangleClassName:className];
+    }
+
+    static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
+    bool isSwift = hasSwiftName || [objectClass isSubclassOfClass:s_swiftObjectClass];
+
+    schema.className = className;
+    schema.objectClass = objectClass;
+    schema.accessorClass = objectClass;
+    schema.isSwiftClass = isSwift;
+
+    // create array of RLMProperties, inserting properties of superclasses first
+    Class cls = objectClass;
+    Class superClass = class_getSuperclass(cls);
+    NSArray *allProperties = @[];
+    while (superClass && superClass != RLMObjectBase.class) {
+        allProperties = [[RLMObjectSchema propertiesForClass:cls isSwift:isSwift]
+                         arrayByAddingObjectsFromArray:allProperties];
+        cls = superClass;
+        superClass = class_getSuperclass(superClass);
+    }
+    NSArray *persistedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
+        return !RLMPropertyTypeIsComputed(property.type);
+    }]];
+    schema.properties = persistedProperties;
+
+    NSArray *computedProperties = [allProperties filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(RLMProperty *property, NSDictionary *) {
+        return RLMPropertyTypeIsComputed(property.type);
+    }]];
+    schema.computedProperties = computedProperties;
+
+    // verify that we didn't add any properties twice due to inheritance
+    if (allProperties.count != [NSSet setWithArray:[allProperties valueForKey:@"name"]].count) {
+        NSCountedSet *countedPropertyNames = [NSCountedSet setWithArray:[allProperties valueForKey:@"name"]];
+        NSArray *duplicatePropertyNames = [countedPropertyNames filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *) {
+            return [countedPropertyNames countForObject:object] > 1;
+        }]].allObjects;
+
+        if (duplicatePropertyNames.count == 1) {
+            @throw RLMException(@"Property '%@' is declared multiple times in the class hierarchy of '%@'", duplicatePropertyNames.firstObject, className);
+        } else {
+            @throw RLMException(@"Object '%@' has properties that are declared multiple times in its class hierarchy: '%@'", className, [duplicatePropertyNames componentsJoinedByString:@"', '"]);
+        }
+    }
+
+    if (NSString *primaryKey = [objectClass primaryKey]) {
+        for (RLMProperty *prop in schema.properties) {
+            if ([primaryKey isEqualToString:prop.name]) {
+                prop.indexed = YES;
+                schema.primaryKeyProperty = prop;
+                break;
+            }
+        }
+
+        if (!schema.primaryKeyProperty) {
+            @throw RLMException(@"Primary key property '%@' does not exist on object '%@'", primaryKey, className);
+        }
+        if (schema.primaryKeyProperty.type != RLMPropertyTypeInt && schema.primaryKeyProperty.type != RLMPropertyTypeString) {
+            @throw RLMException(@"Property '%@' cannot be made the primary key of '%@' because it is not a 'string' or 'int' property.",
+                                primaryKey, className);
+        }
+    }
+
+    for (RLMProperty *prop in schema.properties) {
+        if (prop.optional && prop.array && (prop.type == RLMPropertyTypeObject || prop.type == RLMPropertyTypeLinkingObjects)) {
+            // FIXME: message is awkward
+            @throw RLMException(@"Property '%@.%@' cannot be made optional because optional '%@' properties are not supported.",
+                                className, prop.name, RLMTypeToString(prop.type));
+        }
+    }
+
+    return schema;
+}
+
++ (NSArray *)propertiesForClass:(Class)objectClass isSwift:(bool)isSwiftClass {
+    Class objectUtil = [objectClass objectUtilClass:isSwiftClass];
+    NSArray *ignoredProperties = [objectUtil ignoredPropertiesForClass:objectClass];
+    NSDictionary *linkingObjectsProperties = [objectUtil linkingObjectsPropertiesForClass:objectClass];
+
+    // For Swift classes we need an instance of the object when parsing properties
+    id swiftObjectInstance = isSwiftClass ? [[objectClass alloc] init] : nil;
+
+    unsigned int count;
+    std::unique_ptr<objc_property_t[], decltype(&free)> props(class_copyPropertyList(objectClass, &count), &free);
+    NSMutableArray<RLMProperty *> *propArray = [NSMutableArray arrayWithCapacity:count];
+    NSSet *indexed = [[NSSet alloc] initWithArray:[objectUtil indexedPropertiesForClass:objectClass]];
+    for (unsigned int i = 0; i < count; i++) {
+        NSString *propertyName = @(property_getName(props[i]));
+        if ([ignoredProperties containsObject:propertyName]) {
+            continue;
+        }
+
+        RLMProperty *prop = nil;
+        if (isSwiftClass) {
+            prop = [[RLMProperty alloc] initSwiftPropertyWithName:propertyName
+                                                          indexed:[indexed containsObject:propertyName]
+                                           linkPropertyDescriptor:linkingObjectsProperties[propertyName]
+                                                         property:props[i]
+                                                         instance:swiftObjectInstance];
+        }
+        else {
+            prop = [[RLMProperty alloc] initWithName:propertyName
+                                             indexed:[indexed containsObject:propertyName]
+                              linkPropertyDescriptor:linkingObjectsProperties[propertyName]
+                                            property:props[i]];
+        }
+
+        if (prop) {
+            [propArray addObject:prop];
+        }
+    }
+
+    if (isSwiftClass) {
+        [self addSwiftProperties:propArray objectUtil:objectUtil instance:swiftObjectInstance indexed:indexed];
+    }
+
+    if (auto requiredProperties = [objectUtil requiredPropertiesForClass:objectClass]) {
+        for (RLMProperty *property in propArray) {
+            bool required = [requiredProperties containsObject:property.name];
+            if (required && property.type == RLMPropertyTypeObject && !property.array) {
+                @throw RLMException(@"Object properties cannot be made required, "
+                                    "but '+[%@ requiredProperties]' included '%@'", objectClass, property.name);
+            }
+            property.optional &= !required;
+        }
+    }
+
+    for (RLMProperty *property in propArray) {
+        if (!property.optional && property.type == RLMPropertyTypeObject && !property.array) {
+            @throw RLMException(@"The `%@.%@` property must be marked as being optional.",
+                                [objectClass className], property.name);
+        }
+    }
+
+    return propArray;
+}
+
++ (void)addSwiftProperties:(NSMutableArray<RLMProperty *> *)propArray
+                objectUtil:(Class)objectUtil
+                  instance:(id)instance
+                   indexed:(NSSet<NSString *> *)indexed {
+    // The property list reported to the obj-c runtime for Swift objects is
+    // incomplete and doesn't include Swift generics like List<> and
+    // RealmOptional<>, and is missing information for some properties that
+    // are reported, such as the difference between `String` and `String?`. To
+    // deal with this, we also get the properties from Swift reflection, and
+    // merge the results.
+
+    NSArray<RLMSwiftPropertyMetadata *> *props = [objectUtil getSwiftProperties:instance];
+    if (!props) {
+        // A Swift subclass of RLMObject, which operates under obj-c rules
+        return;
+    }
+
+    // Track the index that we expect the next property to go in, for inserting
+    // generic properties into the correct place
+    NSUInteger nextIndex = 0;
+    for (RLMSwiftPropertyMetadata *md in props) {
+        // In theory existing should only ever be nextIndex or NSNotFound, and
+        // this search is just a waste of time.
+        // FIXME: verify if this is actually true
+        NSUInteger existing = [propArray indexOfObjectPassingTest:^(RLMProperty *obj, NSUInteger, BOOL *) {
+            return [obj.name isEqualToString:md.propertyName];
+        }];
+
+        switch (md.kind) {
+            case RLMSwiftPropertyKindList: // List<>
+                [propArray insertObject:[[RLMProperty alloc] initSwiftListPropertyWithName:md.propertyName
+                                                                                  instance:instance]
+                                atIndex:nextIndex];
+                break;
+            case RLMSwiftPropertyKindLinkingObjects: { // LinkingObjects<>
+                Ivar ivar = class_getInstanceVariable([instance class], md.propertyName.UTF8String);
+                [propArray insertObject:[[RLMProperty alloc] initSwiftLinkingObjectsPropertyWithName:md.propertyName
+                                                                                                ivar:ivar
+                                                                                     objectClassName:md.className
+                                                                              linkOriginPropertyName:md.linkedPropertyName]
+                                atIndex:nextIndex];
+                break;
+            }
+            case RLMSwiftPropertyKindOptional: {
+                if (existing != NSNotFound) {
+                    // String?, Data?, Date? with a non-nil default value
+                    // We already know about this property from obj-c and we
+                    // defaulted to optional, so nothing to do
+                    break;
+                }
+
+                // RealmOptional<>
+                Ivar ivar = class_getInstanceVariable([instance class], md.propertyName.UTF8String);
+                BOOL isIndexed = [indexed containsObject:md.propertyName];
+                [propArray insertObject:[[RLMProperty alloc] initSwiftOptionalPropertyWithName:md.propertyName
+                                                                                       indexed:isIndexed
+                                                                                          ivar:ivar
+                                                                                  propertyType:md.propertyType]
+                                atIndex:nextIndex];
+                break;
+            }
+
+            case RLMSwiftPropertyKindOther:
+            case RLMSwiftPropertyKindNilLiteralOptional:
+                // This might be a property which wasn't reported to obj-c and
+                // isn't one of our supported generic types, in which case we
+                // ignore it
+                if (existing == NSNotFound) {
+                    --nextIndex;
+                }
+                // or it might be a String?, Data?, Date? or object field with
+                // a nil default value
+                else if (md.kind == RLMSwiftPropertyKindNilLiteralOptional) {
+                    propArray[existing].optional = true;
+                }
+                // or it may be some non-optional property which may have been
+                // previously marked as optional due to that being the default
+                // in obj-c
+                else {
+                    propArray[existing].optional = false;
+                }
+                break;
+        }
+
+        ++nextIndex;
+    }
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+    RLMObjectSchema *schema = [[RLMObjectSchema allocWithZone:zone] init];
+    schema->_objectClass = _objectClass;
+    schema->_className = _className;
+    schema->_objectClass = _objectClass;
+    schema->_accessorClass = _objectClass;
+    schema->_unmanagedClass = _unmanagedClass;
+    schema->_isSwiftClass = _isSwiftClass;
+
+    // call property setter to reset map and primary key
+    schema.properties = [[NSArray allocWithZone:zone] initWithArray:_properties copyItems:YES];
+    schema.computedProperties = [[NSArray allocWithZone:zone] initWithArray:_computedProperties copyItems:YES];
+
+    return schema;
+}
+
+- (BOOL)isEqualToObjectSchema:(RLMObjectSchema *)objectSchema {
+    if (objectSchema.properties.count != _properties.count) {
+        return NO;
+    }
+
+    if (![_properties isEqualToArray:objectSchema.properties]) {
+        return NO;
+    }
+    if (![_computedProperties isEqualToArray:objectSchema.computedProperties]) {
+        return NO;
+    }
+
+    return YES;
+}
+
+- (NSString *)description {
+    NSMutableString *propertiesString = [NSMutableString string];
+    for (RLMProperty *property in self.properties) {
+        [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
+    }
+    for (RLMProperty *property in self.computedProperties) {
+        [propertiesString appendFormat:@"\t%@\n", [property.description stringByReplacingOccurrencesOfString:@"\n" withString:@"\n\t"]];
+    }
+    return [NSString stringWithFormat:@"%@ {\n%@}", self.className, propertiesString];
+}
+
+- (NSString *)objectName {
+    return [self.objectClass _realmObjectName] ?: _className;
+}
+
+- (realm::ObjectSchema)objectStoreCopy {
+    ObjectSchema objectSchema;
+    objectSchema.name = self.objectName.UTF8String;
+    objectSchema.primary_key = _primaryKeyProperty ? _primaryKeyProperty.name.UTF8String : "";
+    for (RLMProperty *prop in _properties) {
+        Property p = [prop objectStoreCopy];
+        p.is_primary = (prop == _primaryKeyProperty);
+        objectSchema.persisted_properties.push_back(std::move(p));
+    }
+    for (RLMProperty *prop in _computedProperties) {
+        objectSchema.computed_properties.push_back([prop objectStoreCopy]);
+    }
+    return objectSchema;
+}
+
++ (instancetype)objectSchemaForObjectStoreSchema:(realm::ObjectSchema const&)objectSchema {
+    RLMObjectSchema *schema = [RLMObjectSchema new];
+    schema.className = @(objectSchema.name.c_str());
+
+    // create array of RLMProperties
+    NSMutableArray *properties = [NSMutableArray arrayWithCapacity:objectSchema.persisted_properties.size()];
+    for (const Property &prop : objectSchema.persisted_properties) {
+        RLMProperty *property = [RLMProperty propertyForObjectStoreProperty:prop];
+        property.isPrimary = (prop.name == objectSchema.primary_key);
+        [properties addObject:property];
+    }
+    schema.properties = properties;
+
+    NSMutableArray *computedProperties = [NSMutableArray arrayWithCapacity:objectSchema.computed_properties.size()];
+    for (const Property &prop : objectSchema.computed_properties) {
+        [computedProperties addObject:[RLMProperty propertyForObjectStoreProperty:prop]];
+    }
+    schema.computedProperties = computedProperties;
+
+    // get primary key from realm metadata
+    if (objectSchema.primary_key.length()) {
+        NSString *primaryKeyString = [NSString stringWithUTF8String:objectSchema.primary_key.c_str()];
+        schema.primaryKeyProperty = schema[primaryKeyString];
+        if (!schema.primaryKeyProperty) {
+            @throw RLMException(@"No property matching primary key '%@'", primaryKeyString);
+        }
+    }
+
+    // for dynamic schema use vanilla RLMDynamicObject accessor classes
+    schema.objectClass = RLMObject.class;
+    schema.accessorClass = RLMDynamicObject.class;
+    schema.unmanagedClass = RLMObject.class;
+
+    return schema;
+}
+
+- (NSArray *)swiftGenericProperties {
+    if (_swiftGenericProperties) {
+        return _swiftGenericProperties;
+    }
+
+    // This check isn't semantically required, but avoiding accessing the local
+    // static helps perf in the obj-c case
+    if (!_isSwiftClass) {
+        return _swiftGenericProperties = @[];
+    }
+
+    // Check if it's a swift class using the obj-c API
+    static Class s_swiftObjectClass = NSClassFromString(@"RealmSwiftObject");
+    if (![_accessorClass isSubclassOfClass:s_swiftObjectClass]) {
+        return _swiftGenericProperties = @[];
+    }
+
+    NSMutableArray *genericProperties = [NSMutableArray new];
+    for (RLMProperty *prop in _properties) {
+        if (prop->_swiftIvar) {
+            [genericProperties addObject:prop];
+        }
+    }
+    // Currently all computed properties are Swift generics
+    [genericProperties addObjectsFromArray:_computedProperties];
+
+    return _swiftGenericProperties = genericProperties;
+}
+
+@end