hessian2/object.hpp (622 lines of code) (raw):
#pragma once
#include <algorithm>
#include <chrono>
#include <functional>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "absl/container/node_hash_map.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "common/common.h"
#include "common/macros.h"
namespace Hessian2 {
constexpr const char* UntypedMapMagicString = "untypedmap";
constexpr const char* UntypedListMagicString = "untypedlist";
constexpr const char* NullMagicString = "null";
template <class T>
class OptRef : public absl::optional<std::reference_wrapper<T>> {
public:
// Inherit absl::optional's constructors.
using absl::optional<std::reference_wrapper<T>>::optional;
// Rewrite operator-> then we can use the OptRef as a pointer and not need
// to use the value().get() to get the inner object. Please ensure that the
// OptRef has_value() will return true before using operator->.
T* operator->() { return &(this->value().get()); }
const T* operator->() const { return &(this->value().get()); }
// To get the pointer of the inner object. If the OptRef has_value() return
// false then this method will return nullptr.
T* ptr() { return this->has_value() ? &(this->value().get()) : nullptr; }
const T* ptr() const {
return this->has_value() ? &(this->value().get()) : nullptr;
}
// To get the reference of the inner object. Please ensure that the OptRef
// has_value() will return true before using these methods.
T& ref() { return this->value().get(); }
const T& ref() const { return this->value().get(); }
};
template <class T>
using OptConstRef = OptRef<const T>;
using Binary = std::vector<uint8_t>;
class Object;
using ObjectPtr = std::unique_ptr<Object>;
#define EmptyToMethod(Name, Type) \
virtual OptConstRef<Type> to##Name() const { return absl::nullopt; } \
virtual OptRef<Type> toMutable##Name() { return absl::nullopt; }
#define ValidToMethod(Name, Type) \
OptConstRef<Type> to##Name() const override { return std::cref(data_); } \
OptRef<Type> toMutable##Name() override { return std::ref(data_); }
class Object {
public:
struct ObjectHasher {
using is_transparent = void;
std::size_t operator()(const ObjectPtr& k) const { return k->hash(); }
// In most of cases, string is used as map key. And by this function, we can
// search map entry by the string view directly and avoid creation of
// StringObject.
std::size_t operator()(absl::string_view k) const {
return absl::Hash<absl::string_view>{}(k);
}
};
struct ObjectEqual {
using is_transparent = void;
bool operator()(const ObjectPtr& left, const ObjectPtr& right) const {
return left->equal(*right);
}
// In most of cases, string is used as map key. And by this function, we can
// search map entry by the string view directly and avoid creation of
// StringObject.
bool operator()(const ObjectPtr& left, absl::string_view right) const {
if (left->type() != Type::String) {
return false;
}
return absl::string_view(left->toString().value().get()) == right;
}
bool operator()(absl::string_view left, const ObjectPtr& right) const {
if (right->type() != Type::String) {
return false;
}
return absl::string_view(right->toString().value().get()) == left;
}
};
struct TypeRef {
bool operator==(const TypeRef& other) const { return other.type_ == type_; }
TypeRef(absl::string_view type) : type_(type) {}
std::string type_;
};
struct RawDefinition {
bool operator==(const RawDefinition& other) const {
return other.type_ == type_ && other.field_names_ == field_names_;
}
bool operator!=(const RawDefinition& other) const {
return !(other == *this);
}
RawDefinition() = default;
RawDefinition(const std::string& type, std::vector<std::string>&& field)
: type_(type), field_names_(std::move(field)) {}
RawDefinition(const RawDefinition& def) = default;
std::string toDebugString() const {
std::ostringstream ostream;
for (auto& o : field_names_) {
ostream << o << " ";
}
return absl::StrFormat("type: %s , field_list: %s", type_, ostream.str());
}
size_t hash() const {
size_t hash = 0;
hash = std::hash<std::string>{}(type_);
Utils::hashCombine(hash, field_names_.size());
return hash;
}
std::string type_;
std::vector<std::string> field_names_;
};
using RawDefinitionSharedPtr = std::shared_ptr<RawDefinition>;
struct Definition {
Definition() = default;
Definition(const RawDefinitionSharedPtr data) : data_(std::move(data)) {}
bool operator==(const Definition& other) const {
return *other.data_ == *data_;
}
bool operator!=(const Definition& other) const { return !(other == *this); }
RawDefinitionSharedPtr data_;
};
// TODO(tianqian.zyf): Check that the definition and values size are
// consistent
struct ClassInstance {
bool operator==(const ClassInstance& other) const {
if (*def_ != *other.def_) {
return false;
}
if (data_.size() != other.data_.size()) {
return false;
}
for (size_t i = 0; i < data_.size(); i++) {
if (!data_[i]->equal(*other.data_[i])) {
return false;
}
}
return true;
}
size_t hash() const {
ABSL_ASSERT(def_);
size_t hash = def_->hash();
Utils::hashCombine(hash, data_.size());
return hash;
}
RawDefinitionSharedPtr def_;
std::vector<ObjectPtr> data_;
};
using UntypedMap =
absl::node_hash_map<ObjectPtr, ObjectPtr, ObjectHasher, ObjectEqual>;
struct TypedMap {
TypedMap() = default;
TypedMap(std::string&& type_name, UntypedMap&& values)
: type_name_(std::move(type_name)),
field_name_and_value_(std::move(values)) {}
bool operator==(const TypedMap& other) const {
if (other.type_name_ != type_name_) {
return false;
}
if (other.field_name_and_value_.size() !=
other.field_name_and_value_.size()) {
return false;
}
for (const auto& elem : field_name_and_value_) {
auto it = other.field_name_and_value_.find(elem.first);
if (it == other.field_name_and_value_.end()) {
return false;
}
if (*(it->second) != *(elem.second)) {
return false;
}
}
return true;
}
std::string type_name_;
UntypedMap field_name_and_value_;
};
using UntypedList = std::vector<ObjectPtr>;
struct TypedList {
TypedList() = default;
TypedList(std::string&& type_name, UntypedList&& values)
: type_name_(std::move(type_name)), values_(std::move(values)) {}
bool operator==(const TypedList& other) const {
if (other.type_name_ != type_name_) {
return false;
}
if (values_.size() != other.values_.size()) {
return false;
}
for (size_t i = 0; i < values_.size(); i++) {
if (*(values_[i]) != *(other.values_[i])) {
return false;
}
}
return true;
}
std::string type_name_;
UntypedList values_;
};
enum class Type : unsigned char {
Binary = 0,
Boolean,
Date,
Double,
Integer,
Long,
Null,
Ref,
String,
TypedList,
UntypedList,
TypedMap,
UntypedMap,
Class,
};
Object() = default;
virtual ~Object() = default;
template <typename T>
T& asType() {
assert(dynamic_cast<T*>(this) != nullptr);
return *static_cast<T*>(this);
}
EmptyToMethod(Boolean, bool);
EmptyToMethod(Integer, int32_t);
EmptyToMethod(Long, int64_t);
EmptyToMethod(Double, double);
EmptyToMethod(Date, std::chrono::milliseconds);
EmptyToMethod(Binary, Binary);
EmptyToMethod(String, std::string);
EmptyToMethod(TypedList, TypedList);
EmptyToMethod(UntypedList, UntypedList);
EmptyToMethod(TypedMap, TypedMap);
EmptyToMethod(UntypedMap, UntypedMap);
EmptyToMethod(ClassInstance, ClassInstance);
virtual absl::optional<Object*> toRefDest() const { return absl::nullopt; }
virtual bool equal(const Object& o) const = 0;
virtual Type type() const = 0;
// Used to provide a hash value for a hash map, but the hash value returned by
// this hash method cannot be used as a unique identifier because its
// implementation has a high probability of colliding.
virtual size_t hash() const = 0;
virtual std::string toDebugString() const {
return absl::StrFormat("Type enum value: %d", static_cast<uint8_t>(type()));
}
bool operator==(const Object& other) const { return equal(other); }
bool operator!=(const Object& other) const { return !equal(other); }
};
#define GENERATE_STD_HASH(DataType) \
size_t hash() const override { return std::hash<DataType>{}(data_); }
#define DO_NOT_GENERATE_HASH(DataType)
#define GENERATE_TRIVIAL_METHOD(ObjectType, DataType, MethodName, \
GENERATE_HASH) \
ValidToMethod(MethodName, DataType); \
Type type() const override { return ObjectType; } \
bool equal(const Object& o) const override { \
if (o.type() != ObjectType) { \
return false; \
} \
ABSL_ASSERT(o.to##MethodName().has_value()); \
return o.to##MethodName().value().get() == data_; \
} \
GENERATE_HASH(DataType);
class NullObject : public Object {
public:
Type type() const override { return Type::Null; }
// Object
size_t hash() const override {
// TODO(tianqian.zyf): Provide a unique hash value.
// GCC does not support std::hash<std::nullptr_t>{}(nullptr),
// so only set a special hash value for NullObject.
return std::hash<std::string>{}(NullMagicString);
}
bool equal(const Object& o) const override { return o.type() == Type::Null; }
std::string toDebugString() const override {
return std::string("Type: Null");
}
};
class RefObject : public Object {
public:
// We need to make sure that the object that the RefObject points to has a
// longer lifetime than the RefObject
RefObject(Object* data) : data_(data) {}
// Object.
Type type() const override { return Type::Ref; }
absl::optional<Object*> toRefDest() const override { return data_; }
bool equal(const Object& o) const override {
return o.type() == Type::Ref && o.toRefDest() == data_;
}
size_t hash() const override { return data_->hash(); }
std::string toDebugString() const override {
return absl::StrFormat("Type: Ref, target address: %p, value[%s]", data_,
data_->toDebugString());
}
private:
Object* data_;
};
class BooleanObject : public Object {
public:
BooleanObject(bool data) : data_(data) {}
// Object
GENERATE_TRIVIAL_METHOD(Type::Boolean, bool, Boolean, GENERATE_STD_HASH);
std::string toDebugString() const override {
return absl::StrFormat("Type: boolean, value[%s]",
data_ ? "true" : "false");
}
private:
bool data_;
};
class IntegerObject : public Object {
public:
IntegerObject(int32_t data) : data_(data) {}
// Object
GENERATE_TRIVIAL_METHOD(Type::Integer, int32_t, Integer, GENERATE_STD_HASH);
std::string toDebugString() const override {
return absl::StrFormat("Type: integer, value[%d]", data_);
}
private:
int32_t data_;
};
class DoubleObject : public Object {
public:
DoubleObject(double data) : data_(data) {}
// Object
GENERATE_TRIVIAL_METHOD(Type::Double, double, Double, GENERATE_STD_HASH);
std::string toDebugString() const override {
return absl::StrFormat("Type: double, value[%f]", data_);
}
private:
double data_;
};
class DateObject : public Object {
public:
DateObject(std::chrono::milliseconds data) : data_(data) {}
// Object
GENERATE_TRIVIAL_METHOD(Type::Date, std::chrono::milliseconds, Date,
DO_NOT_GENERATE_HASH)
size_t hash() const override { return std::hash<size_t>{}(data_.count()); }
std::string toDebugString() const override {
return absl::StrFormat("Type: double, value[%d ms]", data_.count());
}
private:
std::chrono::milliseconds data_;
};
class LongObject : public Object {
public:
LongObject(int64_t data) : data_(data) {}
// Object
GENERATE_TRIVIAL_METHOD(Type::Long, int64_t, Long, GENERATE_STD_HASH);
std::string toDebugString() const override {
return absl::StrFormat("Type: long, value[%d]", data_);
}
private:
int64_t data_;
};
class BinaryObject : public Object {
public:
BinaryObject(Binary&& data) : data_(std::move(data)) {}
BinaryObject(const Binary& data) : data_(data) {}
BinaryObject(std::unique_ptr<Binary>&& data) : data_(std::move(*data)) {}
virtual ~BinaryObject() = default;
GENERATE_TRIVIAL_METHOD(Type::Binary, Binary, Binary, DO_NOT_GENERATE_HASH);
size_t hash() const override {
// TODO(tianqian.zyf:) Reduce CPU overhead associated with hash calculations
static size_t hash = 0;
if (hash != 0) {
return hash;
}
hash = data_.size();
for (auto& i : data_) {
Utils::hashCombine(hash, i);
}
return hash;
}
std::string toDebugString() const override {
// By default, only the first 16 bytes are output
size_t limit_len = 16;
std::ostringstream ostream;
if (data_.size() <= limit_len) {
limit_len = data_.size();
}
for (size_t i = 0; i < limit_len; i++) {
ostream << std::hex << data_[i] << " ";
}
return absl::StrFormat("Type: binary, size[%d], value[%s]", data_.size(),
ostream.str());
}
Binary::iterator begin() { return data_.begin(); }
Binary::iterator end() { return data_.end(); }
Binary::const_iterator begin() const { return data_.cbegin(); }
Binary::const_iterator end() const { return data_.cend(); }
private:
Binary data_;
DISALLOW_COPY_AND_ASSIGN(BinaryObject);
};
class StringObject : public Object {
public:
StringObject(absl::string_view data) : data_(data) {}
StringObject(std::unique_ptr<std::string>&& data) : data_(std::move(*data)) {}
// Object
GENERATE_TRIVIAL_METHOD(Type::String, std::string, String,
DO_NOT_GENERATE_HASH);
size_t hash() const override {
// Using absl::Hash for StringObject. Then we can find StringObject by the
// key of type absl::string derectly in the UntypedMap.
return absl::Hash<absl::string_view>{}(data_);
}
std::string toDebugString() const override {
return absl::StrFormat("Type: string, value[%s]", data_);
}
std::string::iterator begin() { return data_.begin(); }
std::string::iterator end() { return data_.end(); }
std::string::const_iterator begin() const { return data_.cbegin(); }
std::string::const_iterator end() const { return data_.cend(); }
private:
std::string data_;
DISALLOW_COPY_AND_ASSIGN(StringObject);
};
class UntypedListObject : public Object {
public:
UntypedListObject() = default;
UntypedListObject(UntypedList&& data) : data_(std::move(data)) {}
void setUntypedList(UntypedList&& data) { data_ = std::move(data); }
// Object
Type type() const override { return Type::UntypedList; }
ValidToMethod(UntypedList, UntypedList);
bool equal(const Object& o) const override {
if (o.type() != type()) {
return false;
}
ABSL_ASSERT(o.toUntypedList().has_value());
auto o_data = o.toUntypedList().value();
if (data_.size() != o_data.get().size()) {
return false;
}
for (size_t i = 0; i < data_.size(); i++) {
if (*data_[i] != *(o_data.get())[i]) {
return false;
}
}
return true;
}
size_t hash() const override {
// Avoid the hash computation overhead by using a magic string and data size
// to calculate the hash value and by comparing operators to handle hash
// conflicts
size_t hash = std::hash<std::string>{}(UntypedListMagicString);
Utils::hashCombine(hash, data_.size());
return hash;
}
std::string toDebugString() const override {
std::ostringstream ostream;
for (auto& o : data_) {
ostream << o->toDebugString() << "\n";
}
return absl::StrFormat("Type: untypedlist, value[%s]", ostream.str());
}
template <typename... _Args>
void emplace_back(_Args&&... __args) {
data_.emplace_back(std::forward<_Args>(__args)...);
}
UntypedList::iterator begin() { return data_.begin(); }
UntypedList::iterator end() { return data_.end(); }
UntypedList::const_iterator begin() const { return data_.cbegin(); }
UntypedList::const_iterator end() const { return data_.cend(); }
Object* get(size_t idx) {
if (idx >= data_.size()) {
return nullptr;
}
return data_[idx].get();
}
private:
UntypedList data_;
DISALLOW_COPY_AND_ASSIGN(UntypedListObject);
};
class TypedListObject : public Object {
public:
TypedListObject() = default;
TypedListObject(Object::TypedList&& data) : data_(std::move(data)) {}
TypedListObject(std::string&& type_name, UntypedList&& item)
: data_(std::move(type_name), std::move(item)) {}
void setTypedList(Object::TypedList&& data) { data_ = std::move(data); }
GENERATE_TRIVIAL_METHOD(Type::TypedList, TypedList, TypedList,
DO_NOT_GENERATE_HASH)
size_t hash() const override {
size_t hash = 0;
hash = std::hash<std::string>{}(data_.type_name_);
Utils::hashCombine(hash, data_.values_.size());
return hash;
}
std::string toDebugString() const override {
std::ostringstream ostream;
for (auto& o : data_.values_) {
ostream << o->toDebugString() << "\n";
}
return absl::StrFormat("Type: typedlist, type[%s], value[%s]",
data_.type_name_, ostream.str());
}
template <typename... _Args>
void emplace_back(_Args&&... __args) {
data_.values_.emplace_back(std::forward<_Args>(__args)...);
}
UntypedList::iterator begin() { return data_.values_.begin(); }
UntypedList::iterator end() { return data_.values_.end(); }
UntypedList::const_iterator begin() const { return data_.values_.cbegin(); }
UntypedList::const_iterator end() const { return data_.values_.cend(); }
Object* get(size_t idx) {
if (idx >= data_.values_.size()) {
return nullptr;
}
return data_.values_[idx].get();
}
void setType(const std::string& type) { data_.type_name_ = type; }
private:
Object::TypedList data_;
DISALLOW_COPY_AND_ASSIGN(TypedListObject);
};
class TypedMapObject : public Object {
public:
TypedMapObject() = default;
TypedMapObject(TypedMap&& data) : data_(std::move(data)) {}
TypedMapObject(std::unique_ptr<TypedMap>&& data) : data_(std::move(*data)) {}
void setTypedMap(TypedMap&& data) { data_ = std::move(data); }
GENERATE_TRIVIAL_METHOD(Type::TypedMap, TypedMap, TypedMap,
DO_NOT_GENERATE_HASH);
size_t hash() const override {
size_t hash = std::hash<std::string>{}(data_.type_name_);
Utils::hashCombine(hash, data_.field_name_and_value_.size());
return hash;
}
std::string toDebugString() const override {
std::ostringstream ostream;
for (auto& o : data_.field_name_and_value_) {
ostream << "key: " << o.first->toDebugString()
<< " value: " << o.second->toDebugString() << "\n";
}
return absl::StrFormat("Type: typedmap, type[%s], value[%s]",
data_.type_name_, ostream.str());
}
template <typename... _Args>
std::pair<UntypedMap::iterator, bool> emplace(_Args&&... __args) {
return data_.field_name_and_value_.emplace(std::forward<_Args>(__args)...);
}
UntypedMap::iterator begin() { return data_.field_name_and_value_.begin(); }
UntypedMap::iterator end() { return data_.field_name_and_value_.end(); }
UntypedMap::const_iterator begin() const {
return data_.field_name_and_value_.cbegin();
}
UntypedMap::const_iterator end() const {
return data_.field_name_and_value_.cend();
}
// TODO(tianqian.zyf): Remove dup implement
const Object* get(const std::string& key) const {
auto o = data_.field_name_and_value_.find(key);
if (o == data_.field_name_and_value_.end()) {
return nullptr;
}
return o->second.get();
}
Object* get(const std::string& key) {
auto o = data_.field_name_and_value_.find(key);
if (o == data_.field_name_and_value_.end()) {
return nullptr;
}
return o->second.get();
}
private:
TypedMap data_;
DISALLOW_COPY_AND_ASSIGN(TypedMapObject);
};
class UntypedMapObject : public Object {
public:
UntypedMapObject() = default;
UntypedMapObject(UntypedMap&& data) : data_(std::move(data)) {}
void setUntypedMap(UntypedMap&& data) { data_ = std::move(data); }
// Object
Type type() const override { return Type::UntypedMap; }
OptConstRef<UntypedMap> toUntypedMap() const override {
return std::cref(data_);
}
OptRef<UntypedMap> toMutableUntypedMap() override { return std::ref(data_); }
bool equal(const Object& o) const override {
if (o.type() != type()) {
return false;
}
ABSL_ASSERT(o.toUntypedMap().has_value());
auto& o_data = o.toUntypedMap().value().get();
if (data_.size() != o_data.size()) {
return false;
}
for (const auto& elem : data_) {
auto it = o_data.find(elem.first);
if (it == o_data.end()) {
return false;
}
if (*(it->second) != *(elem.second)) {
return false;
}
}
return true;
}
size_t hash() const override {
// The overhead of calculating hash for map type is too high, and the map
// itself is unordered, so it is difficult to get a stable hash value, so
// use the magic string and data size to compute hash for map type.
size_t hash = std::hash<std::string>{}(UntypedMapMagicString);
Utils::hashCombine(hash, data_.size());
return hash;
}
std::string toDebugString() const override {
std::ostringstream ostream;
for (auto& o : data_) {
ostream << "key: " << o.first->toDebugString()
<< " value: " << o.second->toDebugString() << "\n";
}
return absl::StrFormat("Type: untypedmap, value[%s]", ostream.str());
}
template <typename... _Args>
std::pair<UntypedMap::iterator, bool> emplace(_Args&&... __args) {
return data_.emplace(std::forward<_Args>(__args)...);
}
UntypedMap::iterator begin() { return data_.begin(); }
UntypedMap::iterator end() { return data_.end(); }
UntypedMap::const_iterator begin() const { return data_.cbegin(); }
UntypedMap::const_iterator end() const { return data_.cend(); }
const Object* get(const std::string& key) const {
auto o = data_.find(key);
if (o == data_.end()) {
return nullptr;
}
return o->second.get();
}
Object* get(const std::string& key) {
auto o = data_.find(key);
if (o == data_.end()) {
return nullptr;
}
return o->second.get();
}
private:
UntypedMap data_;
DISALLOW_COPY_AND_ASSIGN(UntypedMapObject);
};
class ClassInstanceObject : public Object {
public:
ClassInstanceObject() = default;
ClassInstanceObject(ClassInstance&& data) : data_(std::move(data)) {}
void setClassInstance(ClassInstance&& data) { data_ = std::move(data); }
// Object
GENERATE_TRIVIAL_METHOD(Type::Class, ClassInstance, ClassInstance,
DO_NOT_GENERATE_HASH)
size_t hash() const override { return data_.hash(); }
std::string toDebugString() const override {
std::ostringstream ostream;
for (auto& o : data_.data_) {
ostream << o->toDebugString() << " ";
}
return absl::StrFormat("Type: classinstance, def[%s], value[%s]",
data_.def_->toDebugString(), ostream.str());
}
private:
ClassInstance data_;
DISALLOW_COPY_AND_ASSIGN(ClassInstanceObject);
};
} // namespace Hessian2