blob: 0b021292d0fc801953c9867d7c954c487f5a1c57 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_
#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_
#include <any>
#include <cassert>
#include <cstdint>
#include <map>
#include <string>
#include <utility>
#include <variant>
#include <vector>
// Unless overridden, attempt to detect the RTTI state from the compiler.
#ifndef FLUTTER_ENABLE_RTTI
#if defined(_MSC_VER)
#ifdef _CPPRTTI
#define FLUTTER_ENABLE_RTTI 1
#endif
#elif defined(__clang__)
#if __has_feature(cxx_rtti)
#define FLUTTER_ENABLE_RTTI 1
#endif
#elif defined(__GNUC__)
#ifdef __GXX_RTTI
#define FLUTTER_ENABLE_RTTI 1
#endif
#endif
#endif // #ifndef FLUTTER_ENABLE_RTTI
namespace flutter {
static_assert(sizeof(double) == 8, "EncodableValue requires a 64-bit double");
// Defining USE_LEGACY_ENCODABLE_VALUE will use the original EncodableValue
// implementation. This is a temporary measure to minimize the impact of the
// breaking change; it will be removed in the future. If you set this, you
// should update your code as soon as possible to use the new std::variant
// version, or it will break when the legacy version is removed.
#ifndef USE_LEGACY_ENCODABLE_VALUE
// A container for arbitrary types in EncodableValue.
//
// This is used in conjunction with StandardCodecExtension to allow using other
// types with a StandardMethodCodec/StandardMessageCodec. It is implicitly
// convertible to EncodableValue, so constructing an EncodableValue from a
// custom type can generally be written as:
// CustomEncodableValue(MyType(...))
// rather than:
// EncodableValue(CustomEncodableValue(MyType(...)))
//
// For extracting recieved custom types, it is implicitly convertible to
// std::any. For example:
// const MyType& my_type_value =
// std::any_cast<MyType>(std::get<CustomEncodableValue>(value));
//
// If RTTI is enabled, different extension types can be checked with type():
// if (custom_value->type() == typeid(SomeData)) { ... }
// Clients that wish to disable RTTI would need to decide on another approach
// for distinguishing types (e.g., in StandardCodecExtension::WriteValueOfType)
// if multiple custom types are needed. For instance, wrapping all of the
// extension types in an EncodableValue-style variant, and only ever storing
// that variant in CustomEncodableValue.
class CustomEncodableValue {
public:
explicit CustomEncodableValue(const std::any& value) : value_(value) {}
~CustomEncodableValue() = default;
// Allow implict conversion to std::any to allow direct use of any_cast.
operator std::any &() { return value_; }
operator const std::any &() const { return value_; }
#if defined(FLUTTER_ENABLE_RTTI) && FLUTTER_ENABLE_RTTI
// Passthrough to std::any's type().
const std::type_info& type() const noexcept { return value_.type(); }
#endif
// This operator exists only to provide a stable ordering for use as a
// std::map key, to satisfy the compiler requirements for EncodableValue.
// It does not attempt to provide useful ordering semantics, and using a
// custom value as a map key is not recommended.
bool operator<(const CustomEncodableValue& other) const {
return this < &other;
}
bool operator==(const CustomEncodableValue& other) const {
return this == &other;
}
private:
std::any value_;
};
class EncodableValue;
// Convenience type aliases.
using EncodableList = std::vector<EncodableValue>;
using EncodableMap = std::map<EncodableValue, EncodableValue>;
namespace internal {
// The base class for EncodableValue. Do not use this directly; it exists only
// for EncodableValue to inherit from.
//
// Do not change the order or indexes of the items here; see the comment on
// EncodableValue
using EncodableValueVariant = std::variant<std::monostate,
bool,
int32_t,
int64_t,
double,
std::string,
std::vector<uint8_t>,
std::vector<int32_t>,
std::vector<int64_t>,
std::vector<double>,
EncodableList,
EncodableMap,
CustomEncodableValue>;
} // namespace internal
// An object that can contain any value or collection type supported by
// Flutter's standard method codec.
//
// For details, see:
// https://api.flutter.dev/flutter/services/StandardMessageCodec-class.html
//
// As an example, the following Dart structure:
// {
// 'flag': true,
// 'name': 'Thing',
// 'values': [1, 2.0, 4],
// }
// would correspond to:
// EncodableValue(EncodableMap{
// {EncodableValue("flag"), EncodableValue(true)},
// {EncodableValue("name"), EncodableValue("Thing")},
// {EncodableValue("values"), EncodableValue(EncodableList{
// EncodableValue(1),
// EncodableValue(2.0),
// EncodableValue(4),
// })},
// })
//
// The primary API surface for this object is std::variant. For instance,
// getting a string value from an EncodableValue, with type checking:
// if (std::holds_alternative<std::string>(value)) {
// std::string some_string = std::get<std::string>(value);
// }
//
// The order/indexes of the variant types is part of the API surface, and is
// guaranteed not to change.
class EncodableValue : public internal::EncodableValueVariant {
public:
// Rely on std::variant for most of the constructors/operators.
using super = internal::EncodableValueVariant;
using super::super;
using super::operator=;
explicit EncodableValue() = default;
// Avoid the C++17 pitfall of conversion from char* to bool. Should not be
// needed for C++20.
explicit EncodableValue(const char* string) : super(std::string(string)) {}
EncodableValue& operator=(const char* other) {
*this = std::string(other);
return *this;
}
// Allow implicit conversion from CustomEncodableValue; the only reason to
// make a CustomEncodableValue (which can only be constructed explicitly) is
// to use it with EncodableValue, so the risk of unintended conversions is
// minimal, and it avoids the need for the verbose:
// EncodableValue(CustomEncodableValue(...)).
EncodableValue(const CustomEncodableValue& v) : super(v) {}
// Override the conversion constructors from std::variant to make them
// explicit, to avoid implicit conversion.
//
// While implicit conversion can be convenient in some cases, it can have very
// surprising effects. E.g., calling a function that takes an EncodableValue
// but accidentally passing an EncodableValue* would, instead of failing to
// compile, go through a pointer->bool->EncodableValue(bool) chain and
// silently call the function with a temp-constructed EncodableValue(true).
template <class T>
constexpr explicit EncodableValue(T&& t) noexcept : super(t) {}
// Returns true if the value is null. Convenience wrapper since unlike the
// other types, std::monostate uses aren't self-documenting.
bool IsNull() const { return std::holds_alternative<std::monostate>(*this); }
// Convience method to simplify handling objects received from Flutter where
// the values may be larger than 32-bit, since they have the same type on the
// Dart side, but will be either 32-bit or 64-bit here depending on the value.
//
// Calling this method if the value doesn't contain either an int32_t or an
// int64_t will throw an exception.
int64_t LongValue() {
if (std::holds_alternative<int32_t>(*this)) {
return std::get<int32_t>(*this);
}
return std::get<int64_t>(*this);
}
};
#else
class EncodableValue;
// Convenience type aliases for list and map EncodableValue types.
using EncodableList = std::vector<EncodableValue>;
using EncodableMap = std::map<EncodableValue, EncodableValue>;
// An object that can contain any value or collection type supported by
// Flutter's standard method codec.
//
// For details, see:
// https://api.flutter.dev/flutter/services/StandardMessageCodec-class.html
//
// As an example, the following Dart structure:
// {
// 'flag': true,
// 'name': 'Thing',
// 'values': [1, 2.0, 4],
// }
// would correspond to:
// EncodableValue(EncodableMap{
// {EncodableValue("flag"), EncodableValue(true)},
// {EncodableValue("name"), EncodableValue("Thing")},
// {EncodableValue("values"), EncodableValue(EncodableList{
// EncodableValue(1),
// EncodableValue(2.0),
// EncodableValue(4),
// })},
// })
class EncodableValue {
public:
// Possible types for an EncodableValue to reperesent.
enum class Type {
kNull, // A null value.
kBool, // A boolean value.
kInt, // A 32-bit integer.
kLong, // A 64-bit integer.
kDouble, // A 64-bit floating point number.
kString, // A string.
kByteList, // A list of bytes.
kIntList, // A list of 32-bit integers.
kLongList, // A list of 64-bit integers.
kDoubleList, // A list of 64-bit floating point numbers.
kList, // A list of EncodableValues.
kMap, // A mapping from EncodableValues to EncodableValues.
};
// Creates an instance representing a null value.
EncodableValue() {}
// Creates an instance representing a bool value.
explicit EncodableValue(bool value) : bool_(value), type_(Type::kBool) {}
// Creates an instance representing a 32-bit integer value.
explicit EncodableValue(int32_t value) : int_(value), type_(Type::kInt) {}
// Creates an instance representing a 64-bit integer value.
explicit EncodableValue(int64_t value) : long_(value), type_(Type::kLong) {}
// Creates an instance representing a 64-bit floating point value.
explicit EncodableValue(double value)
: double_(value), type_(Type::kDouble) {}
// Creates an instance representing a string value.
explicit EncodableValue(const char* value)
: string_(new std::string(value)), type_(Type::kString) {}
// Creates an instance representing a string value.
explicit EncodableValue(const std::string& value)
: string_(new std::string(value)), type_(Type::kString) {}
// Creates an instance representing a list of bytes.
explicit EncodableValue(std::vector<uint8_t> list)
: byte_list_(new std::vector<uint8_t>(std::move(list))),
type_(Type::kByteList) {}
// Creates an instance representing a list of 32-bit integers.
explicit EncodableValue(std::vector<int32_t> list)
: int_list_(new std::vector<int32_t>(std::move(list))),
type_(Type::kIntList) {}
// Creates an instance representing a list of 64-bit integers.
explicit EncodableValue(std::vector<int64_t> list)
: long_list_(new std::vector<int64_t>(std::move(list))),
type_(Type::kLongList) {}
// Creates an instance representing a list of 64-bit floating point values.
explicit EncodableValue(std::vector<double> list)
: double_list_(new std::vector<double>(std::move(list))),
type_(Type::kDoubleList) {}
// Creates an instance representing a list of EncodableValues.
explicit EncodableValue(EncodableList list)
: list_(new EncodableList(std::move(list))), type_(Type::kList) {}
// Creates an instance representing a map from EncodableValues to
// EncodableValues.
explicit EncodableValue(EncodableMap map)
: map_(new EncodableMap(std::move(map))), type_(Type::kMap) {}
// Convience constructor for creating default value of the given type.
//
// Collections types will be empty, numeric types will be 0, strings will be
// empty, and booleans will be false. For non-collection types, prefer using
// the value-based constructor with an explicit value for clarity.
explicit EncodableValue(Type type) : type_(type) {
switch (type_) {
case Type::kNull:
break;
case Type::kBool:
bool_ = false;
break;
case Type::kInt:
int_ = 0;
break;
case Type::kLong:
long_ = 0;
break;
case Type::kDouble:
double_ = 0.0;
break;
case Type::kString:
string_ = new std::string();
break;
case Type::kByteList:
byte_list_ = new std::vector<uint8_t>();
break;
case Type::kIntList:
int_list_ = new std::vector<int32_t>();
break;
case Type::kLongList:
long_list_ = new std::vector<int64_t>();
break;
case Type::kDoubleList:
double_list_ = new std::vector<double>();
break;
case Type::kList:
list_ = new std::vector<EncodableValue>();
break;
case Type::kMap:
map_ = new std::map<EncodableValue, EncodableValue>();
break;
}
}
~EncodableValue() { DestroyValue(); }
EncodableValue(const EncodableValue& other) {
DestroyValue();
type_ = other.type_;
switch (type_) {
case Type::kNull:
break;
case Type::kBool:
bool_ = other.bool_;
break;
case Type::kInt:
int_ = other.int_;
break;
case Type::kLong:
long_ = other.long_;
break;
case Type::kDouble:
double_ = other.double_;
break;
case Type::kString:
string_ = new std::string(*other.string_);
break;
case Type::kByteList:
byte_list_ = new std::vector<uint8_t>(*other.byte_list_);
break;
case Type::kIntList:
int_list_ = new std::vector<int32_t>(*other.int_list_);
break;
case Type::kLongList:
long_list_ = new std::vector<int64_t>(*other.long_list_);
break;
case Type::kDoubleList:
double_list_ = new std::vector<double>(*other.double_list_);
break;
case Type::kList:
list_ = new std::vector<EncodableValue>(*other.list_);
break;
case Type::kMap:
map_ = new std::map<EncodableValue, EncodableValue>(*other.map_);
break;
}
}
EncodableValue(EncodableValue&& other) noexcept { *this = std::move(other); }
EncodableValue& operator=(const EncodableValue& other) {
if (&other == this) {
return *this;
}
using std::swap;
EncodableValue temp(other);
swap(*this, temp);
return *this;
}
EncodableValue& operator=(EncodableValue&& other) noexcept {
if (&other == this) {
return *this;
}
DestroyValue();
type_ = other.type_;
switch (type_) {
case Type::kNull:
break;
case Type::kBool:
bool_ = other.bool_;
break;
case Type::kInt:
int_ = other.int_;
break;
case Type::kLong:
long_ = other.long_;
break;
case Type::kDouble:
double_ = other.double_;
break;
case Type::kString:
string_ = other.string_;
break;
case Type::kByteList:
byte_list_ = other.byte_list_;
break;
case Type::kIntList:
int_list_ = other.int_list_;
break;
case Type::kLongList:
long_list_ = other.long_list_;
break;
case Type::kDoubleList:
double_list_ = other.double_list_;
break;
case Type::kList:
list_ = other.list_;
break;
case Type::kMap:
map_ = other.map_;
break;
}
// Ensure that destruction doesn't run on the source of the move.
other.type_ = Type::kNull;
return *this;
}
// Allow assigning any value type that can be used for a constructor.
template <typename T>
EncodableValue& operator=(const T& value) {
*this = EncodableValue(value);
return *this;
}
// This operator exists only to provide a stable ordering for use as a
// std::map key. It does not attempt to provide useful ordering semantics.
// Notably:
// - Numeric values are not guaranteed any ordering across numeric types.
// E.g., 1 as a Long may sort after 100 as an Int.
// - Collection types use pointer equality, rather than value. This means that
// multiple collections with the same values will end up as separate keys
// in a map (consistent with default Dart Map behavior).
bool operator<(const EncodableValue& other) const {
if (type_ != other.type_) {
return type_ < other.type_;
}
switch (type_) {
case Type::kNull:
return false;
case Type::kBool:
return bool_ < other.bool_;
case Type::kInt:
return int_ < other.int_;
case Type::kLong:
return long_ < other.long_;
case Type::kDouble:
return double_ < other.double_;
case Type::kString:
return *string_ < *other.string_;
case Type::kByteList:
case Type::kIntList:
case Type::kLongList:
case Type::kDoubleList:
case Type::kList:
case Type::kMap:
return this < &other;
}
assert(false);
return false;
}
// Returns the bool value this object represents.
//
// It is a programming error to call this unless IsBool() is true.
bool BoolValue() const {
assert(IsBool());
return bool_;
}
// Returns the 32-bit integer value this object represents.
//
// It is a programming error to call this unless IsInt() is true.
int32_t IntValue() const {
assert(IsInt());
return int_;
}
// Returns the 64-bit integer value this object represents.
//
// It is a programming error to call this unless IsLong() or IsInt() is true.
//
// Note that calling this function on an Int value is the only case where
// a *Value() function can be called without the corresponding Is*() being
// true. This is to simplify handling objects received from Flutter where the
// values may be larger than 32-bit, since they have the same type on the Dart
// side, but will be either 32-bit or 64-bit here depending on the value.
int64_t LongValue() const {
assert(IsLong() || IsInt());
if (IsLong()) {
return long_;
}
return int_;
}
// Returns the double value this object represents.
//
// It is a programming error to call this unless IsDouble() is true.
double DoubleValue() const {
assert(IsDouble());
return double_;
}
// Returns the string value this object represents.
//
// It is a programming error to call this unless IsString() is true.
const std::string& StringValue() const {
assert(IsString());
return *string_;
}
// Returns the byte list this object represents.
//
// It is a programming error to call this unless IsByteList() is true.
const std::vector<uint8_t>& ByteListValue() const {
assert(IsByteList());
return *byte_list_;
}
// Returns the byte list this object represents.
//
// It is a programming error to call this unless IsByteList() is true.
std::vector<uint8_t>& ByteListValue() {
assert(IsByteList());
return *byte_list_;
}
// Returns the 32-bit integer list this object represents.
//
// It is a programming error to call this unless IsIntList() is true.
const std::vector<int32_t>& IntListValue() const {
assert(IsIntList());
return *int_list_;
}
// Returns the 32-bit integer list this object represents.
//
// It is a programming error to call this unless IsIntList() is true.
std::vector<int32_t>& IntListValue() {
assert(IsIntList());
return *int_list_;
}
// Returns the 64-bit integer list this object represents.
//
// It is a programming error to call this unless IsLongList() is true.
const std::vector<int64_t>& LongListValue() const {
assert(IsLongList());
return *long_list_;
}
// Returns the 64-bit integer list this object represents.
//
// It is a programming error to call this unless IsLongList() is true.
std::vector<int64_t>& LongListValue() {
assert(IsLongList());
return *long_list_;
}
// Returns the double list this object represents.
//
// It is a programming error to call this unless IsDoubleList() is true.
const std::vector<double>& DoubleListValue() const {
assert(IsDoubleList());
return *double_list_;
}
// Returns the double list this object represents.
//
// It is a programming error to call this unless IsDoubleList() is true.
std::vector<double>& DoubleListValue() {
assert(IsDoubleList());
return *double_list_;
}
// Returns the list of EncodableValues this object represents.
//
// It is a programming error to call this unless IsList() is true.
const EncodableList& ListValue() const {
assert(IsList());
return *list_;
}
// Returns the list of EncodableValues this object represents.
//
// It is a programming error to call this unless IsList() is true.
EncodableList& ListValue() {
assert(IsList());
return *list_;
}
// Returns the map of EncodableValue : EncodableValue pairs this object
// represent.
//
// It is a programming error to call this unless IsMap() is true.
const EncodableMap& MapValue() const {
assert(IsMap());
return *map_;
}
// Returns the map of EncodableValue : EncodableValue pairs this object
// represent.
//
// It is a programming error to call this unless IsMap() is true.
EncodableMap& MapValue() {
assert(IsMap());
return *map_;
}
// Returns true if this represents a null value.
bool IsNull() const { return type_ == Type::kNull; }
// Returns true if this represents a bool value.
bool IsBool() const { return type_ == Type::kBool; }
// Returns true if this represents a 32-bit integer value.
bool IsInt() const { return type_ == Type::kInt; }
// Returns true if this represents a 64-bit integer value.
bool IsLong() const { return type_ == Type::kLong; }
// Returns true if this represents a double value.
bool IsDouble() const { return type_ == Type::kDouble; }
// Returns true if this represents a string value.
bool IsString() const { return type_ == Type::kString; }
// Returns true if this represents a list of bytes.
bool IsByteList() const { return type_ == Type::kByteList; }
// Returns true if this represents a list of 32-bit integers.
bool IsIntList() const { return type_ == Type::kIntList; }
// Returns true if this represents a list of 64-bit integers.
bool IsLongList() const { return type_ == Type::kLongList; }
// Returns true if this represents a list of doubles.
bool IsDoubleList() const { return type_ == Type::kDoubleList; }
// Returns true if this represents a list of EncodableValues.
bool IsList() const { return type_ == Type::kList; }
// Returns true if this represents a map of EncodableValue : EncodableValue
// pairs.
bool IsMap() const { return type_ == Type::kMap; }
// Returns the type this value represents.
//
// This is primarily intended for use with switch(); for individual checks,
// prefer an Is*() call.
Type type() const { return type_; }
private:
// Performs any cleanup necessary for the active union value. This must be
// called before assigning a new value, and on object destruction.
//
// After calling this, type_ will alway be kNull.
void DestroyValue() {
switch (type_) {
case Type::kNull:
case Type::kBool:
case Type::kInt:
case Type::kLong:
case Type::kDouble:
break;
case Type::kString:
delete string_;
break;
case Type::kByteList:
delete byte_list_;
break;
case Type::kIntList:
delete int_list_;
break;
case Type::kLongList:
delete long_list_;
break;
case Type::kDoubleList:
delete double_list_;
break;
case Type::kList:
delete list_;
break;
case Type::kMap:
delete map_;
break;
}
type_ = Type::kNull;
}
// The anonymous union that stores the represented value. Accessing any of
// these entries other than the one that corresponds to the current value of
// |type_| has undefined behavior.
//
// Pointers are used for the non-POD types to avoid making the overall size
// of the union unnecessarily large.
//
// TODO: Replace this with std::variant once c++17 is available.
union {
bool bool_;
int32_t int_;
int64_t long_;
double double_;
std::string* string_;
std::vector<uint8_t>* byte_list_;
std::vector<int32_t>* int_list_;
std::vector<int64_t>* long_list_;
std::vector<double>* double_list_;
std::vector<EncodableValue>* list_;
std::map<EncodableValue, EncodableValue>* map_;
};
// The currently active union entry.
Type type_ = Type::kNull;
};
#endif
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_ENCODABLE_VALUE_H_