1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2014 Realm Inc.
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
17 ////////////////////////////////////////////////////////////////////////////
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"
30 #import "shared_realm.hpp"
32 #import <realm/mixed.hpp>
33 #import <realm/table_view.hpp>
35 #include <sys/sysctl.h>
36 #include <sys/types.h>
38 #if !defined(REALM_COCOA_VERSION)
39 #import "RLMVersion.h"
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);
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') {
63 if (numberIsInteger(obj)) {
64 int value = [obj intValue];
65 return value == 0 || value == 1;
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])));
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);
100 static inline RLMArray *asRLMArray(__unsafe_unretained id const value) {
101 return RLMDynamicCast<RLMArray>(value) ?: RLMDynamicCast<RLMListBase>(value)._rlmArray;
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]);
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)) {
118 if (auto rlmArray = asRLMArray(value)) {
119 return checkArrayType(rlmArray, type, optional, objectClassName);
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)) {
130 if (!value || value == NSNull.null) {
137 case RLMPropertyTypeString:
138 return [value isKindOfClass:[NSString class]];
139 case RLMPropertyTypeBool:
140 if ([value isKindOfClass:[NSNumber class]]) {
141 return numberIsBool(value);
144 case RLMPropertyTypeDate:
145 return [value isKindOfClass:[NSDate class]];
146 case RLMPropertyTypeInt:
147 if (NSNumber *number = RLMDynamicCast<NSNumber>(value)) {
148 return numberIsInteger(number);
151 case RLMPropertyTypeFloat:
152 if (NSNumber *number = RLMDynamicCast<NSNumber>(value)) {
153 return numberIsFloat(number);
156 case RLMPropertyTypeDouble:
157 if (NSNumber *number = RLMDynamicCast<NSNumber>(value)) {
158 return numberIsDouble(number);
161 case RLMPropertyTypeData:
162 return [value isKindOfClass:[NSData class]];
163 case RLMPropertyTypeAny:
165 case RLMPropertyTypeLinkingObjects:
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];
174 @throw RLMException(@"Invalid RLMPropertyType specified");
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 '%@.%@'.",
182 prop.objectClassName ?: RLMTypeToString(prop.type), prop.optional ? "?" : "",
183 prop.array ? " array" : "", objectSchema.className, prop.name);
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
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) {
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);
204 if (!validateObjects && prop.type == RLMPropertyTypeObject) {
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);
218 for (id value in obj) {
219 if (!RLMValidateValue(value, prop.type, prop.optional, false, prop.objectClassName)) {
220 RLMThrowTypeError(value, objectSchema, prop);
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) {
233 if (RLMIsObjectValidForProperty(obj, prop)) {
237 RLMThrowTypeError(obj, objectSchema, prop);
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);
245 NSDictionary *RLMDefaultValuesForObjectSchema(__unsafe_unretained RLMObjectSchema *const objectSchema) {
246 if (!objectSchema.isSwiftClass) {
247 return [objectSchema.objectClass defaultPropertyValues];
250 NSMutableDictionary *defaults = nil;
251 if ([objectSchema.objectClass isSubclassOfClass:RLMObject.class]) {
252 defaults = [NSMutableDictionary dictionaryWithDictionary:[objectSchema.objectClass defaultPropertyValues]];
255 defaults = [NSMutableDictionary dictionary];
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];
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];
272 NSException *e = [NSException exceptionWithName:RLMExceptionName
278 NSException *RLMException(NSString *fmt, ...) {
281 NSException *e = RLMException([[NSString alloc] initWithFormat:fmt arguments:args], @{});
286 NSException *RLMException(std::exception const& exception) {
287 return RLMException(@"%s", exception.what());
290 NSError *RLMMakeError(RLMError code, std::exception const& exception) {
291 return [NSError errorWithDomain:RLMErrorDomain
293 userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
294 @"Error Code": @(code)}];
297 NSError *RLMMakeError(RLMError code, const realm::util::File::AccessError& exception) {
298 return [NSError errorWithDomain:RLMErrorDomain
300 userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
301 NSFilePathErrorKey: @(exception.get_path().c_str()),
302 @"Error Code": @(code)}];
305 NSError *RLMMakeError(RLMError code, const realm::RealmFileException& exception) {
306 NSString *underlying = @(exception.underlying().c_str());
307 return [NSError errorWithDomain:RLMErrorDomain
309 userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
310 NSFilePathErrorKey: @(exception.path().c_str()),
311 @"Error Code": @(code),
312 @"Underlying": underlying.length == 0 ? @"n/a" : underlying}];
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;
320 return [NSError errorWithDomain:errorDomain
321 code:exception.code().value()
322 userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
323 @"Error Code": @(exception.code().value()),
324 @"Category": category}];
327 void RLMSetErrorOrThrow(NSError *error, NSError **outError) {
332 NSString *msg = error.localizedDescription;
333 if (error.userInfo[NSFilePathErrorKey]) {
334 msg = [NSString stringWithFormat:@"%@: %@", error.userInfo[NSFilePathErrorKey], error.localizedDescription];
336 @throw RLMException(msg, @{NSUnderlyingErrorKey: error});
340 BOOL RLMIsDebuggerAttached()
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));
356 return (info.kp_proc.p_flag & P_TRACED) != 0;
359 BOOL RLMIsRunningInPlayground() {
360 return [[NSBundle mainBundle].bundleIdentifier hasPrefix:@"com.apple.dt.playground."];
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:
382 @throw RLMException(@"Invalid data type for RLMPropertyTypeAny property.");
386 NSString *RLMDefaultDirectoryForBundleIdentifier(NSString *bundleIdentifier) {
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];
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;
404 if (!bundleIdentifier) {
405 bundleIdentifier = [NSBundle mainBundle].executablePath.lastPathComponent;
408 path = [path stringByAppendingPathComponent:bundleIdentifier];
411 [[NSFileManager defaultManager] createDirectoryAtPath:path
412 withIntermediateDirectories:YES