#include NS_ASSUME_NONNULL_BEGIN namespace mbgl { namespace style { namespace conversion { // A wrapper class for `id`, so as not to confuse ARC. class Holder { public: Holder(const id v) : value(v) {} const id value; }; template <> class ConversionTraits { public: static bool isUndefined(const Holder& holder) { const id value = holder.value; return !value || value == [NSNull null]; } static bool isArray(const Holder& holder) { const id value = holder.value; return [value isKindOfClass:[NSArray class]]; } static bool isObject(const Holder& holder) { const id value = holder.value; return [value isKindOfClass:[NSDictionary class]]; } static std::size_t arrayLength(const Holder& holder) { const id value = holder.value; NSCAssert([value isKindOfClass:[NSArray class]], @"Value must be an NSArray for getLength()."); NSArray *array = value; auto length = [array count]; NSCAssert(length <= std::numeric_limits::max(), @"Array length out of bounds."); return length; } static Holder arrayMember(const Holder& holder, std::size_t i) { const id value = holder.value; NSCAssert([value isKindOfClass:[NSArray class]], @"Value must be an NSArray for get(int)."); NSCAssert(i < NSUIntegerMax, @"Index must be less than NSUIntegerMax"); return {[value objectAtIndex: i]}; } static optional objectMember(const Holder& holder, const char *key) { const id value = holder.value; NSCAssert([value isKindOfClass:[NSDictionary class]], @"Value must be an NSDictionary for get(string)."); NSObject *member = [value objectForKey: @(key)]; if (member && member != [NSNull null]) { return {member}; } else { return {}; } } // Compiler is wrong about `Fn` parameter missing a nullability specifier. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnullability-completeness" template static optional eachMember(const Holder&, Fn&&) { #pragma clang diagnostic pop // Not implemented (unneeded for MGLStyleFunction conversion). NSCAssert(NO, @"eachMember not implemented"); return {}; } static optional toBool(const Holder& holder) { const id value = holder.value; if (_isBool(value)) { return ((NSNumber *)value).boolValue; } else { return {}; } } static optional toNumber(const Holder& holder) { const id value = holder.value; if (_isNumber(value)) { return ((NSNumber *)value).floatValue; } else { return {}; } } static optional toDouble(const Holder& holder) { const id value = holder.value; if (_isNumber(value)) { return ((NSNumber *)value).doubleValue; } else { return {}; } } static optional toString(const Holder& holder) { const id value = holder.value; if (_isString(value)) { return std::string(static_cast([value UTF8String])); } else { return {}; } } static optional toValue(const Holder& holder) { const id value = holder.value; if (isUndefined(value)) { return {}; } else if (_isBool(value)) { return { *toBool(holder) }; } else if ( _isString(value)) { return { *toString(holder) }; } else if (_isNumber(value)) { // Need to cast to a double here as the float is otherwise considered a bool... return { static_cast(*toNumber(holder)) }; } else { return {}; } } static optional toGeoJSON(const Holder& holder, Error& error) { error = { "toGeoJSON not implemented" }; return {}; } private: static bool _isBool(const id value) { if (![value isKindOfClass:[NSNumber class]]) return false; // char: 32-bit boolean // BOOL: 64-bit boolean NSNumber *number = value; return ((strcmp([number objCType], @encode(char)) == 0) || (strcmp([number objCType], @encode(BOOL)) == 0)); } static bool _isNumber(const id value) { return [value isKindOfClass:[NSNumber class]] && !_isBool(value); } static bool _isString(const id value) { return [value isKindOfClass:[NSString class]]; } }; inline Convertible makeConvertible(const id value) { return Convertible(Holder(value)); } } // namespace conversion } // namespace style } // namespace mbgl NS_ASSUME_NONNULL_END