Newer
Older
bremer-ios-app / Pods / Realm / include / RLMUtil.hpp
////////////////////////////////////////////////////////////////////////////
//
// 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 <Realm/RLMConstants.h>
#import <Realm/RLMSwiftValueStorage.h>
#import <Realm/RLMValue.h>

#import <realm/array.hpp>
#import <realm/binary_data.hpp>
#import <realm/object-store/object.hpp>
#import <realm/string_data.hpp>
#import <realm/timestamp.hpp>
#import <realm/util/file.hpp>

#import <objc/runtime.h>
#import <os/lock.h>

namespace realm {
class Decimal128;
class Exception;
class Mixed;
}

class RLMClassInfo;

@class RLMObjectSchema;
@class RLMProperty;

__attribute__((format(NSString, 1, 2)))
NSException *RLMException(NSString *fmt, ...);
NSException *RLMException(std::exception const& exception);
NSException *RLMException(realm::Exception const& exception);

void RLMSetErrorOrThrow(NSError *error, NSError **outError);

RLM_HIDDEN_BEGIN

// returns if the object can be inserted as the given type
BOOL RLMIsObjectValidForProperty(id obj, RLMProperty *prop);
// throw an exception if the object is not a valid value for the property
void RLMValidateValueForProperty(id obj, RLMObjectSchema *objectSchema,
                                 RLMProperty *prop, bool validateObjects=false);
id RLMValidateValue(id value, RLMPropertyType type, bool optional, bool collection,
                    NSString *objectClassName);

void RLMThrowTypeError(id obj, RLMObjectSchema *objectSchema, RLMProperty *prop);

// gets default values for the given schema (+defaultPropertyValues)
// merges with native property defaults if Swift class
NSDictionary *RLMDefaultValuesForObjectSchema(RLMObjectSchema *objectSchema);

BOOL RLMIsDebuggerAttached();
BOOL RLMIsRunningInPlayground();

// C version of isKindOfClass
static inline BOOL RLMIsKindOfClass(Class class1, Class class2) {
    while (class1) {
        if (class1 == class2) return YES;
        class1 = class_getSuperclass(class1);
    }
    return NO;
}

template<typename T>
static inline T *RLMDynamicCast(__unsafe_unretained id obj) {
    if ([obj isKindOfClass:[T class]]) {
        return obj;
    }
    return nil;
}

static inline id RLMCoerceToNil(__unsafe_unretained id obj) {
    if (static_cast<id>(obj) == NSNull.null) {
        return nil;
    }
    else if (__unsafe_unretained auto optional = RLMDynamicCast<RLMSwiftValueStorage>(obj)) {
        return RLMCoerceToNil(RLMGetSwiftValueStorage(optional));
    }
    return obj;
}

template<typename T>
static inline T RLMCoerceToNil(__unsafe_unretained T obj) {
    return RLMCoerceToNil(static_cast<id>(obj));
}

id<NSFastEnumeration> RLMAsFastEnumeration(id obj);
id RLMBridgeSwiftValue(id obj);

bool RLMIsSwiftObjectClass(Class cls);

// String conversion utilities
static inline NSString *RLMStringDataToNSString(realm::StringData stringData) {
    static_assert(sizeof(NSUInteger) >= sizeof(size_t),
                  "Need runtime overflow check for size_t to NSUInteger conversion");
    if (stringData.is_null()) {
        return nil;
    }
    else {
        return [[NSString alloc] initWithBytes:stringData.data()
                                        length:stringData.size()
                                      encoding:NSUTF8StringEncoding];
    }
}

static inline NSString *RLMStringViewToNSString(std::string_view stringView) {
    if (stringView.size() == 0) {
        return nil;
    }
    return [[NSString alloc] initWithBytes:stringView.data()
                                    length:stringView.size()
                                  encoding:NSUTF8StringEncoding];
}

static inline realm::StringData RLMStringDataWithNSString(__unsafe_unretained NSString *const string) {
    static_assert(sizeof(size_t) >= sizeof(NSUInteger),
                  "Need runtime overflow check for NSUInteger to size_t conversion");
    return realm::StringData(string.UTF8String,
                             [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
}

// Binary conversion utilities
static inline NSData *RLMBinaryDataToNSData(realm::BinaryData binaryData) {
    return binaryData ? [NSData dataWithBytes:binaryData.data() length:binaryData.size()] : nil;
}

static inline realm::BinaryData RLMBinaryDataForNSData(__unsafe_unretained NSData *const data) {
    // this is necessary to ensure that the empty NSData isn't treated by core as the null realm::BinaryData
    // because data.bytes == 0 when data.length == 0
    // the casting bit ensures that we create a data with a non-null pointer
    auto bytes = static_cast<const char *>(data.bytes) ?: static_cast<char *>((__bridge void *)data);
    return realm::BinaryData(bytes, data.length);
}

// Date conversion utilities
// These use the reference date and shift the seconds rather than just getting
// the time interval since the epoch directly to avoid losing sub-second precision
static inline NSDate *RLMTimestampToNSDate(realm::Timestamp ts) NS_RETURNS_RETAINED {
    if (ts.is_null())
        return nil;
    auto timeInterval = ts.get_seconds() - NSTimeIntervalSince1970 + ts.get_nanoseconds() / 1'000'000'000.0;
    return [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:timeInterval];
}

static inline realm::Timestamp RLMTimestampForNSDate(__unsafe_unretained NSDate *const date) {
    if (!date)
        return {};
    auto timeInterval = date.timeIntervalSinceReferenceDate;
    if (isnan(timeInterval))
        return {0, 0}; // Arbitrary choice

    // Clamp dates that we can't represent as a Timestamp to the maximum value
    if (timeInterval >= std::numeric_limits<int64_t>::max() - NSTimeIntervalSince1970)
        return {std::numeric_limits<int64_t>::max(), 1'000'000'000 - 1};
    if (timeInterval - NSTimeIntervalSince1970 < std::numeric_limits<int64_t>::min())
        return {std::numeric_limits<int64_t>::min(), -1'000'000'000 + 1};

    auto seconds = static_cast<int64_t>(timeInterval);
    auto nanoseconds = static_cast<int32_t>((timeInterval - seconds) * 1'000'000'000.0);
    seconds += static_cast<int64_t>(NSTimeIntervalSince1970);

    // Seconds and nanoseconds have to have the same sign
    if (nanoseconds < 0 && seconds > 0) {
        nanoseconds += 1'000'000'000;
        --seconds;
    }
    return {seconds, nanoseconds};
}

static inline NSUInteger RLMConvertNotFound(size_t index) {
    return index == realm::not_found ? NSNotFound : index;
}

static inline void RLMNSStringToStdString(std::string &out, NSString *in) {
    if (!in)
        return;
    
    out.resize([in maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
    if (out.empty()) {
        return;
    }

    NSUInteger size = out.size();
    [in getBytes:&out[0]
       maxLength:size
      usedLength:&size
        encoding:NSUTF8StringEncoding
         options:0 range:{0, in.length} remainingRange:nullptr];
    out.resize(size);
}

realm::Mixed RLMObjcToMixed(__unsafe_unretained id value,
                            __unsafe_unretained RLMRealm *realm=nil,
                            realm::CreatePolicy createPolicy={});
id RLMMixedToObjc(realm::Mixed const& value,
                  __unsafe_unretained RLMRealm *realm=nil,
                  RLMClassInfo *classInfo=nullptr);

realm::Decimal128 RLMObjcToDecimal128(id value);
realm::UUID RLMObjcToUUID(__unsafe_unretained id const value);

// Given a bundle identifier, return the base directory on the disk within which Realm database and support files should
// be stored.
FOUNDATION_EXTERN RLM_VISIBLE
NSString *RLMDefaultDirectoryForBundleIdentifier(NSString *bundleIdentifier);

// Get a NSDateFormatter for ISO8601-formatted strings
NSDateFormatter *RLMISO8601Formatter();

template<typename Fn>
static auto RLMTranslateError(Fn&& fn) {
    try {
        return fn();
    }
    catch (std::exception const& e) {
        @throw RLMException(e);
    }
}

static inline bool numberIsInteger(__unsafe_unretained NSNumber *const obj) {
    char data_type = [obj objCType][0];
    return data_type == *@encode(bool) ||
           data_type == *@encode(char) ||
           data_type == *@encode(short) ||
           data_type == *@encode(int) ||
           data_type == *@encode(long) ||
           data_type == *@encode(long long) ||
           data_type == *@encode(unsigned short) ||
           data_type == *@encode(unsigned int) ||
           data_type == *@encode(unsigned long) ||
           data_type == *@encode(unsigned long long);
}

static inline bool numberIsBool(__unsafe_unretained NSNumber *const obj) {
    // @encode(BOOL) is 'B' on iOS 64 and 'c'
    // objcType is always 'c'. Therefore compare to "c".
    if ([obj objCType][0] == 'c') {
        return true;
    }

    if (numberIsInteger(obj)) {
        int value = [obj intValue];
        return value == 0 || value == 1;
    }

    return false;
}

static inline bool numberIsFloat(__unsafe_unretained NSNumber *const obj) {
    char data_type = [obj objCType][0];
    return data_type == *@encode(float) ||
           data_type == *@encode(short) ||
           data_type == *@encode(int) ||
           data_type == *@encode(long) ||
           data_type == *@encode(long long) ||
           data_type == *@encode(unsigned short) ||
           data_type == *@encode(unsigned int) ||
           data_type == *@encode(unsigned long) ||
           data_type == *@encode(unsigned long long) ||
           // A double is like float if it fits within float bounds or is NaN.
           (data_type == *@encode(double) && (ABS([obj doubleValue]) <= FLT_MAX || isnan([obj doubleValue])));
}

static inline bool numberIsDouble(__unsafe_unretained NSNumber *const obj) {
    char data_type = [obj objCType][0];
    return data_type == *@encode(double) ||
           data_type == *@encode(float) ||
           data_type == *@encode(short) ||
           data_type == *@encode(int) ||
           data_type == *@encode(long) ||
           data_type == *@encode(long long) ||
           data_type == *@encode(unsigned short) ||
           data_type == *@encode(unsigned int) ||
           data_type == *@encode(unsigned long) ||
           data_type == *@encode(unsigned long long);
}

class RLMUnfairMutex {
public:
    RLMUnfairMutex() = default;

    void lock() noexcept {
        os_unfair_lock_lock(&_lock);
    }

    bool try_lock() noexcept {
        return os_unfair_lock_trylock(&_lock);
    }

    void unlock() noexcept {
        os_unfair_lock_unlock(&_lock);
    }

private:
    os_unfair_lock _lock = OS_UNFAIR_LOCK_INIT;
    RLMUnfairMutex(RLMUnfairMutex const&) = delete;
    RLMUnfairMutex& operator=(RLMUnfairMutex const&) = delete;
};

RLM_HIDDEN_END