added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / RLMUtil.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 "RLMUtil.hpp"
20
21 #import "RLMArray_Private.hpp"
22 #import "RLMListBase.h"
23 #import "RLMObjectSchema_Private.hpp"
24 #import "RLMObjectStore.h"
25 #import "RLMObject_Private.hpp"
26 #import "RLMProperty_Private.h"
27 #import "RLMSchema_Private.h"
28 #import "RLMSwiftSupport.h"
29
30 #import "shared_realm.hpp"
31
32 #import <realm/mixed.hpp>
33 #import <realm/table_view.hpp>
34
35 #include <sys/sysctl.h>
36 #include <sys/types.h>
37
38 #if !defined(REALM_COCOA_VERSION)
39 #import "RLMVersion.h"
40 #endif
41
42 static inline bool numberIsInteger(__unsafe_unretained NSNumber *const obj) {
43     char data_type = [obj objCType][0];
44     return data_type == *@encode(bool) ||
45            data_type == *@encode(char) ||
46            data_type == *@encode(short) ||
47            data_type == *@encode(int) ||
48            data_type == *@encode(long) ||
49            data_type == *@encode(long long) ||
50            data_type == *@encode(unsigned short) ||
51            data_type == *@encode(unsigned int) ||
52            data_type == *@encode(unsigned long) ||
53            data_type == *@encode(unsigned long long);
54 }
55
56 static inline bool numberIsBool(__unsafe_unretained NSNumber *const obj) {
57     // @encode(BOOL) is 'B' on iOS 64 and 'c'
58     // objcType is always 'c'. Therefore compare to "c".
59     if ([obj objCType][0] == 'c') {
60         return true;
61     }
62
63     if (numberIsInteger(obj)) {
64         int value = [obj intValue];
65         return value == 0 || value == 1;
66     }
67
68     return false;
69 }
70
71 static inline bool numberIsFloat(__unsafe_unretained NSNumber *const obj) {
72     char data_type = [obj objCType][0];
73     return data_type == *@encode(float) ||
74            data_type == *@encode(short) ||
75            data_type == *@encode(int) ||
76            data_type == *@encode(long) ||
77            data_type == *@encode(long long) ||
78            data_type == *@encode(unsigned short) ||
79            data_type == *@encode(unsigned int) ||
80            data_type == *@encode(unsigned long) ||
81            data_type == *@encode(unsigned long long) ||
82            // A double is like float if it fits within float bounds or is NaN.
83            (data_type == *@encode(double) && (ABS([obj doubleValue]) <= FLT_MAX || isnan([obj doubleValue])));
84 }
85
86 static inline bool numberIsDouble(__unsafe_unretained NSNumber *const obj) {
87     char data_type = [obj objCType][0];
88     return data_type == *@encode(double) ||
89            data_type == *@encode(float) ||
90            data_type == *@encode(short) ||
91            data_type == *@encode(int) ||
92            data_type == *@encode(long) ||
93            data_type == *@encode(long long) ||
94            data_type == *@encode(unsigned short) ||
95            data_type == *@encode(unsigned int) ||
96            data_type == *@encode(unsigned long) ||
97            data_type == *@encode(unsigned long long);
98 }
99
100 static inline RLMArray *asRLMArray(__unsafe_unretained id const value) {
101     return RLMDynamicCast<RLMArray>(value) ?: RLMDynamicCast<RLMListBase>(value)._rlmArray;
102 }
103
104 static inline bool checkArrayType(__unsafe_unretained RLMArray *const array,
105                                   RLMPropertyType type, bool optional,
106                                   __unsafe_unretained NSString *const objectClassName) {
107     return array.type == type && array.optional == optional
108         && (type != RLMPropertyTypeObject || [array.objectClassName isEqualToString:objectClassName]);
109 }
110
111 BOOL RLMValidateValue(__unsafe_unretained id const value,
112                       RLMPropertyType type, bool optional, bool array,
113                       __unsafe_unretained NSString *const objectClassName) {
114     if (optional && !RLMCoerceToNil(value)) {
115         return YES;
116     }
117     if (array) {
118         if (auto rlmArray = asRLMArray(value)) {
119             return checkArrayType(rlmArray, type, optional, objectClassName);
120         }
121         if ([value conformsToProtocol:@protocol(NSFastEnumeration)]) {
122             // check each element for compliance
123             for (id el in (id<NSFastEnumeration>)value) {
124                 if (!RLMValidateValue(el, type, optional, false, objectClassName)) {
125                     return NO;
126                 }
127             }
128             return YES;
129         }
130         if (!value || value == NSNull.null) {
131             return YES;
132         }
133         return NO;
134     }
135
136     switch (type) {
137         case RLMPropertyTypeString:
138             return [value isKindOfClass:[NSString class]];
139         case RLMPropertyTypeBool:
140             if ([value isKindOfClass:[NSNumber class]]) {
141                 return numberIsBool(value);
142             }
143             return NO;
144         case RLMPropertyTypeDate:
145             return [value isKindOfClass:[NSDate class]];
146         case RLMPropertyTypeInt:
147             if (NSNumber *number = RLMDynamicCast<NSNumber>(value)) {
148                 return numberIsInteger(number);
149             }
150             return NO;
151         case RLMPropertyTypeFloat:
152             if (NSNumber *number = RLMDynamicCast<NSNumber>(value)) {
153                 return numberIsFloat(number);
154             }
155             return NO;
156         case RLMPropertyTypeDouble:
157             if (NSNumber *number = RLMDynamicCast<NSNumber>(value)) {
158                 return numberIsDouble(number);
159             }
160             return NO;
161         case RLMPropertyTypeData:
162             return [value isKindOfClass:[NSData class]];
163         case RLMPropertyTypeAny:
164             return NO;
165         case RLMPropertyTypeLinkingObjects:
166             return YES;
167         case RLMPropertyTypeObject: {
168             // only NSNull, nil, or objects which derive from RLMObject and match the given
169             // object class are valid
170             RLMObjectBase *objBase = RLMDynamicCast<RLMObjectBase>(value);
171             return objBase && [objBase->_objectSchema.className isEqualToString:objectClassName];
172         }
173     }
174     @throw RLMException(@"Invalid RLMPropertyType specified");
175 }
176
177 void RLMThrowTypeError(__unsafe_unretained id const obj,
178                        __unsafe_unretained RLMObjectSchema *const objectSchema,
179                        __unsafe_unretained RLMProperty *const prop) {
180     @throw RLMException(@"Invalid value '%@' of type '%@' for '%@%s'%s property '%@.%@'.",
181                         obj, [obj class],
182                         prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "",
183                         prop.array ? " array" : "", objectSchema.className, prop.name);
184 }
185
186 void RLMValidateValueForProperty(__unsafe_unretained id const obj,
187                                  __unsafe_unretained RLMObjectSchema *const objectSchema,
188                                  __unsafe_unretained RLMProperty *const prop,
189                                  bool validateObjects) {
190     // This duplicates a lot of the checks in RLMIsObjectValidForProperty()
191     // for the sake of more specific error messages
192     if (prop.array) {
193         // nil is considered equivalent to an empty array for historical reasons
194         // since we don't support null arrays (only arrays containing null),
195         // it's not worth the BC break to change this
196         if (!obj || obj == NSNull.null) {
197             return;
198         }
199         if (![obj conformsToProtocol:@protocol(NSFastEnumeration)]) {
200             @throw RLMException(@"Invalid value (%@) for '%@%s' array property '%@.%@': value is not enumerable.",
201                                 obj, prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "",
202                                 objectSchema.className, prop.name);
203         }
204         if (!validateObjects && prop.type == RLMPropertyTypeObject) {
205             return;
206         }
207
208         if (RLMArray *array = asRLMArray(obj)) {
209             if (!checkArrayType(array, prop.type, prop.optional, prop.objectClassName)) {
210                 @throw RLMException(@"RLMArray<%@%s> does not match expected type '%@%s' for property '%@.%@'.",
211                                     array.objectClassName ?: RLMTypeToString(array.type), array.optional ? "?" : "",
212                                     prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "",
213                                     objectSchema.className, prop.name);
214             }
215             return;
216         }
217
218         for (id value in obj) {
219             if (!RLMValidateValue(value, prop.type, prop.optional, false, prop.objectClassName)) {
220                 RLMThrowTypeError(value, objectSchema, prop);
221             }
222         }
223         return;
224     }
225
226     // For create() we want to skip the validation logic for objects because
227     // we allow much fuzzier matching (any KVC-compatible object with at least
228     // all the non-defaulted fields), and all the logic for that lives in the
229     // object store rather than here
230     if (prop.type == RLMPropertyTypeObject && !validateObjects) {
231         return;
232     }
233     if (RLMIsObjectValidForProperty(obj, prop)) {
234         return;
235     }
236
237     RLMThrowTypeError(obj, objectSchema, prop);
238 }
239
240 BOOL RLMIsObjectValidForProperty(__unsafe_unretained id const obj,
241                                  __unsafe_unretained RLMProperty *const property) {
242     return RLMValidateValue(obj, property.type, property.optional, property.array, property.objectClassName);
243 }
244
245 NSDictionary *RLMDefaultValuesForObjectSchema(__unsafe_unretained RLMObjectSchema *const objectSchema) {
246     if (!objectSchema.isSwiftClass) {
247         return [objectSchema.objectClass defaultPropertyValues];
248     }
249
250     NSMutableDictionary *defaults = nil;
251     if ([objectSchema.objectClass isSubclassOfClass:RLMObject.class]) {
252         defaults = [NSMutableDictionary dictionaryWithDictionary:[objectSchema.objectClass defaultPropertyValues]];
253     }
254     else {
255         defaults = [NSMutableDictionary dictionary];
256     }
257     RLMObject *defaultObject = [[objectSchema.objectClass alloc] init];
258     for (RLMProperty *prop in objectSchema.properties) {
259         if (!defaults[prop.name] && defaultObject[prop.name]) {
260             defaults[prop.name] = defaultObject[prop.name];
261         }
262     }
263     return defaults;
264 }
265
266 static NSException *RLMException(NSString *reason, NSDictionary *additionalUserInfo) {
267     NSMutableDictionary *userInfo = @{RLMRealmVersionKey: REALM_COCOA_VERSION,
268                                       RLMRealmCoreVersionKey: @REALM_VERSION}.mutableCopy;
269     if (additionalUserInfo != nil) {
270         [userInfo addEntriesFromDictionary:additionalUserInfo];
271     }
272     NSException *e = [NSException exceptionWithName:RLMExceptionName
273                                              reason:reason
274                                            userInfo:userInfo];
275     return e;
276 }
277
278 NSException *RLMException(NSString *fmt, ...) {
279     va_list args;
280     va_start(args, fmt);
281     NSException *e = RLMException([[NSString alloc] initWithFormat:fmt arguments:args], @{});
282     va_end(args);
283     return e;
284 }
285
286 NSException *RLMException(std::exception const& exception) {
287     return RLMException(@"%s", exception.what());
288 }
289
290 NSError *RLMMakeError(RLMError code, std::exception const& exception) {
291     return [NSError errorWithDomain:RLMErrorDomain
292                                code:code
293                            userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
294                                       @"Error Code": @(code)}];
295 }
296
297 NSError *RLMMakeError(RLMError code, const realm::util::File::AccessError& exception) {
298     return [NSError errorWithDomain:RLMErrorDomain
299                                code:code
300                            userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
301                                       NSFilePathErrorKey: @(exception.get_path().c_str()),
302                                       @"Error Code": @(code)}];
303 }
304
305 NSError *RLMMakeError(RLMError code, const realm::RealmFileException& exception) {
306     NSString *underlying = @(exception.underlying().c_str());
307     return [NSError errorWithDomain:RLMErrorDomain
308                                code:code
309                            userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
310                                       NSFilePathErrorKey: @(exception.path().c_str()),
311                                       @"Error Code": @(code),
312                                       @"Underlying": underlying.length == 0 ? @"n/a" : underlying}];
313 }
314
315 NSError *RLMMakeError(std::system_error const& exception) {
316     BOOL isGenericCategoryError = (exception.code().category() == std::generic_category());
317     NSString *category = @(exception.code().category().name());
318     NSString *errorDomain = isGenericCategoryError ? NSPOSIXErrorDomain : RLMUnknownSystemErrorDomain;
319
320     return [NSError errorWithDomain:errorDomain
321                                code:exception.code().value()
322                            userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
323                                       @"Error Code": @(exception.code().value()),
324                                       @"Category": category}];
325 }
326
327 void RLMSetErrorOrThrow(NSError *error, NSError **outError) {
328     if (outError) {
329         *outError = error;
330     }
331     else {
332         NSString *msg = error.localizedDescription;
333         if (error.userInfo[NSFilePathErrorKey]) {
334             msg = [NSString stringWithFormat:@"%@: %@", error.userInfo[NSFilePathErrorKey], error.localizedDescription];
335         }
336         @throw RLMException(msg, @{NSUnderlyingErrorKey: error});
337     }
338 }
339
340 BOOL RLMIsDebuggerAttached()
341 {
342     int name[] = {
343         CTL_KERN,
344         KERN_PROC,
345         KERN_PROC_PID,
346         getpid()
347     };
348
349     struct kinfo_proc info;
350     size_t info_size = sizeof(info);
351     if (sysctl(name, sizeof(name)/sizeof(name[0]), &info, &info_size, NULL, 0) == -1) {
352         NSLog(@"sysctl() failed: %s", strerror(errno));
353         return false;
354     }
355
356     return (info.kp_proc.p_flag & P_TRACED) != 0;
357 }
358
359 BOOL RLMIsRunningInPlayground() {
360     return [[NSBundle mainBundle].bundleIdentifier hasPrefix:@"com.apple.dt.playground."];
361 }
362
363 id RLMMixedToObjc(realm::Mixed const& mixed) {
364     switch (mixed.get_type()) {
365         case realm::type_String:
366             return RLMStringDataToNSString(mixed.get_string());
367         case realm::type_Int:
368             return @(mixed.get_int());
369         case realm::type_Float:
370             return @(mixed.get_float());
371         case realm::type_Double:
372             return @(mixed.get_double());
373         case realm::type_Bool:
374             return @(mixed.get_bool());
375         case realm::type_Timestamp:
376             return RLMTimestampToNSDate(mixed.get_timestamp());
377         case realm::type_Binary:
378             return RLMBinaryDataToNSData(mixed.get_binary());
379         case realm::type_Link:
380         case realm::type_LinkList:
381         default:
382             @throw RLMException(@"Invalid data type for RLMPropertyTypeAny property.");
383     }
384 }
385
386 NSString *RLMDefaultDirectoryForBundleIdentifier(NSString *bundleIdentifier) {
387 #if TARGET_OS_TV
388     (void)bundleIdentifier;
389     // tvOS prohibits writing to the Documents directory, so we use the Library/Caches directory instead.
390     return NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
391 #elif TARGET_OS_IPHONE
392     (void)bundleIdentifier;
393     // On iOS the Documents directory isn't user-visible, so put files there
394     return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
395 #else
396     // On OS X it is, so put files in Application Support. If we aren't running
397     // in a sandbox, put it in a subdirectory based on the bundle identifier
398     // to avoid accidentally sharing files between applications
399     NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0];
400     if (![[NSProcessInfo processInfo] environment][@"APP_SANDBOX_CONTAINER_ID"]) {
401         if (!bundleIdentifier) {
402             bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
403         }
404         if (!bundleIdentifier) {
405             bundleIdentifier = [NSBundle mainBundle].executablePath.lastPathComponent;
406         }
407
408         path = [path stringByAppendingPathComponent:bundleIdentifier];
409
410         // create directory
411         [[NSFileManager defaultManager] createDirectoryAtPath:path
412                                   withIntermediateDirectories:YES
413                                                    attributes:nil
414                                                         error:nil];
415     }
416     return path;
417 #endif
418 }