From f2a28ee4720f34efae516c04eccb97e6820644b2 Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Wed, 13 Dec 2017 21:53:18 +0800 Subject: [PATCH] Add archiver example A simple (de)serialization framework using DOM and SAX API --- example/CMakeLists.txt | 2 + example/archiver/archiver.cpp | 292 ++++++++++++++++++++++++++++++ example/archiver/archiver.h | 139 ++++++++++++++ example/archiver/archivertest.cpp | 281 ++++++++++++++++++++++++++++ 4 files changed, 714 insertions(+) create mode 100644 example/archiver/archiver.cpp create mode 100644 example/archiver/archiver.h create mode 100644 example/archiver/archivertest.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index e00f77aab7..ff54199337 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -32,6 +32,8 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") endif() +add_executable(archivertest archiver/archiver.cpp archiver/archivertest.cpp) + foreach (example ${EXAMPLES}) add_executable(${example} ${example}/${example}.cpp) endforeach() diff --git a/example/archiver/archiver.cpp b/example/archiver/archiver.cpp new file mode 100644 index 0000000000..59ae4c4101 --- /dev/null +++ b/example/archiver/archiver.cpp @@ -0,0 +1,292 @@ +#include "archiver.h" +#include +#include +#include "rapidjson/document.h" +#include "rapidjson/prettywriter.h" +#include "rapidjson/stringbuffer.h" + +using namespace rapidjson; + +struct JsonReaderStackItem { + enum State { + BeforeStart, //!< An object/array is in the stack but it is not yet called by StartObject()/StartArray(). + Started, //!< An object/array is called by StartObject()/StartArray(). + Closed //!< An array is closed after read all element, but before EndArray(). + }; + + JsonReaderStackItem(const Value* value, State state) : value(value), state(state), index() {} + + const Value* value; + State state; + SizeType index; // For array iteration +}; + +typedef std::stack JsonReaderStack; + +#define DOCUMENT reinterpret_cast(mDocument) +#define STACK (reinterpret_cast(mStack)) +#define TOP (STACK->top()) +#define CURRENT (*TOP.value) + +JsonReader::JsonReader(const char* json) : mDocument(), mStack(), mError(false) { + mDocument = new Document; + DOCUMENT->Parse(json); + if (DOCUMENT->HasParseError()) + mError = true; + else { + mStack = new JsonReaderStack; + STACK->push(JsonReaderStackItem(DOCUMENT, JsonReaderStackItem::BeforeStart)); + } +} + +JsonReader::~JsonReader() { + delete DOCUMENT; + delete STACK; +} + +// Archive concept +JsonReader& JsonReader::StartObject() { + if (!mError) { + if (CURRENT.IsObject() && TOP.state == JsonReaderStackItem::BeforeStart) + TOP.state = JsonReaderStackItem::Started; + else + mError = true; + } + return *this; +} + +JsonReader& JsonReader::EndObject() { + if (!mError) { + if (CURRENT.IsObject() && TOP.state == JsonReaderStackItem::Started) + Next(); + else + mError = true; + } + return *this; +} + +JsonReader& JsonReader::Member(const char* name) { + if (!mError) { + if (CURRENT.IsObject() && TOP.state == JsonReaderStackItem::Started) { + Value::ConstMemberIterator memberItr = CURRENT.FindMember(name); + if (memberItr != CURRENT.MemberEnd()) + STACK->push(JsonReaderStackItem(&memberItr->value, JsonReaderStackItem::BeforeStart)); + else + mError = true; + } + else + mError = true; + } + return *this; +} + +bool JsonReader::HasMember(const char* name) const { + if (!mError && CURRENT.IsObject() && TOP.state == JsonReaderStackItem::Started) + return CURRENT.HasMember(name); + return false; +} + +JsonReader& JsonReader::StartArray(size_t* size) { + if (!mError) { + if (CURRENT.IsArray() && TOP.state == JsonReaderStackItem::BeforeStart) { + TOP.state = JsonReaderStackItem::Started; + if (size) + *size = CURRENT.Size(); + + if (!CURRENT.Empty()) { + const Value* value = &CURRENT[TOP.index]; + STACK->push(JsonReaderStackItem(value, JsonReaderStackItem::BeforeStart)); + } + else + TOP.state = JsonReaderStackItem::Closed; + } + else + mError = true; + } + return *this; +} + +JsonReader& JsonReader::EndArray() { + if (!mError) { + if (CURRENT.IsArray() && TOP.state == JsonReaderStackItem::Closed) + Next(); + else + mError = true; + } + return *this; +} + +JsonReader& JsonReader::operator&(bool& b) { + if (!mError) { + if (CURRENT.IsBool()) { + b = CURRENT.GetBool(); + Next(); + } + else + mError = true; + } + return *this; +} + +JsonReader& JsonReader::operator&(unsigned& u) { + if (!mError) { + if (CURRENT.IsUint()) { + u = CURRENT.GetUint(); + Next(); + } + else + mError = true; + } + return *this; +} + +JsonReader& JsonReader::operator&(int& i) { + if (!mError) { + if (CURRENT.IsInt()) { + i = CURRENT.GetInt(); + Next(); + } + else + mError = true; + } + return *this; +} + +JsonReader& JsonReader::operator&(double& d) { + if (!mError) { + if (CURRENT.IsNumber()) { + d = CURRENT.GetDouble(); + Next(); + } + else + mError = true; + } + return *this; +} + +JsonReader& JsonReader::operator&(std::string& s) { + if (!mError) { + if (CURRENT.IsString()) { + s = CURRENT.GetString(); + Next(); + } + else + mError = true; + } + return *this; +} + +JsonReader& JsonReader::SetNull() { + // This function is for JsonWriter only. + mError = true; + return *this; +} + +void JsonReader::Next() { + if (!mError) { + assert(!STACK->empty()); + STACK->pop(); + + if (!STACK->empty() && CURRENT.IsArray()) { + if (TOP.state == JsonReaderStackItem::Started) { // Otherwise means reading array item pass end + if (TOP.index < CURRENT.Size() - 1) { + const Value* value = &CURRENT[++TOP.index]; + STACK->push(JsonReaderStackItem(value, JsonReaderStackItem::BeforeStart)); + } + else + TOP.state = JsonReaderStackItem::Closed; + } + else + mError = true; + } + } +} + +#undef DOCUMENT +#undef STACK +#undef TOP +#undef CURRENT + +//////////////////////////////////////////////////////////////////////////////// +// JsonWriter + +#define WRITER reinterpret_cast*>(mWriter) +#define STREAM reinterpret_cast(mStream) + +JsonWriter::JsonWriter() : mWriter(), mStream() { + mStream = new StringBuffer; + mWriter = new PrettyWriter(*STREAM); +} + +JsonWriter::~JsonWriter() { + delete WRITER; + delete STREAM; +} + +const char* JsonWriter::GetString() const { + return STREAM->GetString(); +} + +JsonWriter& JsonWriter::StartObject() { + WRITER->StartObject(); + return *this; +} + +JsonWriter& JsonWriter::EndObject() { + WRITER->EndObject(); + return *this; +} + +JsonWriter& JsonWriter::Member(const char* name) { + WRITER->String(name, static_cast(strlen(name))); + return *this; +} + +bool JsonWriter::HasMember(const char*) const { + // This function is for JsonReader only. + assert(false); + return false; +} + +JsonWriter& JsonWriter::StartArray(size_t*) { + WRITER->StartArray(); + return *this; +} + +JsonWriter& JsonWriter::EndArray() { + WRITER->EndArray(); + return *this; +} + +JsonWriter& JsonWriter::operator&(bool& b) { + WRITER->Bool(b); + return *this; +} + +JsonWriter& JsonWriter::operator&(unsigned& u) { + WRITER->Uint(u); + return *this; +} + +JsonWriter& JsonWriter::operator&(int& i) { + WRITER->Int(i); + return *this; +} + +JsonWriter& JsonWriter::operator&(double& d) { + WRITER->Double(d); + return *this; +} + +JsonWriter& JsonWriter::operator&(std::string& s) { + WRITER->String(s.c_str(), static_cast(s.size())); + return *this; +} + +JsonWriter& JsonWriter::SetNull() { + WRITER->Null(); + return *this; +} + +#undef STREAM +#undef WRITER diff --git a/example/archiver/archiver.h b/example/archiver/archiver.h new file mode 100644 index 0000000000..c7e74f0c22 --- /dev/null +++ b/example/archiver/archiver.h @@ -0,0 +1,139 @@ +#ifndef ARCHIVER_H_ +#define ARCHIVER_H_ + +#include +#include + +/** +\class Archiver +\brief Archiver concept + +Archiver can be a reader or writer for serialization or deserialization respectively. + +class Archiver { +public: + /// \returns true if the archiver is in normal state. false if it has errors. + operator bool() const; + + /// Starts an object + Archiver& StartObject(); + + /// After calling StartObject(), assign a member with a name + Archiver& Member(const char* name); + + /// After calling StartObject(), check if a member presents + bool HasMember(const char* name) const; + + /// Ends an object + Archiver& EndObject(); + + /// Starts an array + /// \param size If Archiver::IsReader is true, the size of array is written. + Archiver& StartArray(size_t* size = 0); + + /// Ends an array + Archiver& EndArray(); + + /// Read/Write primitive types. + Archiver& operator&(bool& b); + Archiver& operator&(unsigned& u); + Archiver& operator&(int& i); + Archiver& operator&(double& d); + Archiver& operator&(std::string& s); + + /// Write primitive types. + Archiver& SetNull(); + + //! Whether it is a reader. + static const bool IsReader; + + //! Whether it is a writer. + static const bool IsWriter; +}; +*/ + +/// Represents a JSON reader which implements Archiver concept. +class JsonReader { +public: + /// Constructor. + /** + \param json A non-const source json string for in-situ parsing. + \note in-situ means the source JSON string will be modified after parsing. + */ + JsonReader(const char* json); + + /// Destructor. + ~JsonReader(); + + // Archive concept + + operator bool() const { return !mError; } + + JsonReader& StartObject(); + JsonReader& Member(const char* name); + bool HasMember(const char* name) const; + JsonReader& EndObject(); + + JsonReader& StartArray(size_t* size = nullptr); + JsonReader& EndArray(); + + JsonReader& operator&(bool& b); + JsonReader& operator&(unsigned& u); + JsonReader& operator&(int& i); + JsonReader& operator&(double& d); + JsonReader& operator&(std::string& s); + + JsonReader& SetNull(); + + static const bool IsReader = true; + static const bool IsWriter = !IsReader; + +private: + void Next(); + + // PIMPL + void* mDocument; ///< DOM result of parsing. + void* mStack; ///< Stack for iterating the DOM + bool mError; ///< Whether an error is occured. +}; + +class JsonWriter { +public: + /// Constructor. + JsonWriter(); + + /// Destructor. + ~JsonWriter(); + + /// Obtains the serialized JSON string. + const char* GetString() const; + + // Archive concept + + operator bool() const { return true; } + + JsonWriter& StartObject(); + JsonWriter& Member(const char* name); + bool HasMember(const char* name) const; + JsonWriter& EndObject(); + + JsonWriter& StartArray(size_t* size = 0); + JsonWriter& EndArray(); + + JsonWriter& operator&(bool& b); + JsonWriter& operator&(unsigned& u); + JsonWriter& operator&(int& i); + JsonWriter& operator&(double& d); + JsonWriter& operator&(std::string& s); + JsonWriter& SetNull(); + + static const bool IsReader = false; + static const bool IsWriter = !IsReader; + +private: + // PIMPL idiom + void* mWriter; ///< JSON writer. + void* mStream; ///< Stream buffer. +}; + +#endif // ARCHIVER_H__ diff --git a/example/archiver/archivertest.cpp b/example/archiver/archivertest.cpp new file mode 100644 index 0000000000..788db36e62 --- /dev/null +++ b/example/archiver/archivertest.cpp @@ -0,0 +1,281 @@ +#include "archiver.h" +#include +#include + +////////////////////////////////////////////////////////////////////////////// +// Test1: simple object + +struct Student { + std::string name; + unsigned age; + double height; + bool canSwim; +}; + +template +Archiver& operator&(Archiver& ar, Student& s) { + ar.StartObject(); + ar.Member("name") & s.name; + ar.Member("age") & s.age; + ar.Member("height") & s.height; + ar.Member("canSwim") & s.canSwim; + return ar.EndObject(); +} + +std::ostream& operator<<(std::ostream& os, const Student& s) { + return os << s.name << " " << s.age << " " << s.height << " " << s.canSwim; +} + +void test1() { + std::string json; + + // Serialize + { + Student s = { "Lua", 9, 150.5, true }; + + JsonWriter writer; + writer & s; + json = writer.GetString(); + std::cout << json << std::endl; + } + + // Deserialize + { + Student s; + JsonReader reader(json.c_str()); + reader & s; + std::cout << s << std::endl; + } +} + +////////////////////////////////////////////////////////////////////////////// +// Test2: std::vector <=> JSON array +// +// You can map a JSON array to other data structures as well + +struct Group { + std::string groupName; + std::vector students; +}; + +template +Archiver& operator&(Archiver& ar, Group& g) { + ar.StartObject(); + + ar.Member("groupName"); + ar & g.groupName; + + ar.Member("students"); + size_t studentCount = g.students.size(); + ar.StartArray(&studentCount); + if (ar.IsReader) + g.students.resize(studentCount); + for (size_t i = 0; i < studentCount; i++) + ar & g.students[i]; + ar.EndArray(); + + return ar.EndObject(); +} + +std::ostream& operator<<(std::ostream& os, const Group& g) { + os << g.groupName << std::endl; + for (std::vector::const_iterator itr = g.students.begin(); itr != g.students.end(); ++itr) + os << *itr << std::endl; + return os; +} + +void test2() { + std::string json; + + // Serialize + { + Group g; + g.groupName = "Rainbow"; + + Student s1 = { "Lua", 9, 150.5, true }; + Student s2 = { "Mio", 7, 120.0, false }; + g.students.push_back(s1); + g.students.push_back(s2); + + JsonWriter writer; + writer & g; + json = writer.GetString(); + std::cout << json << std::endl; + } + + // Deserialize + { + Group g; + JsonReader reader(json.c_str()); + reader & g; + std::cout << g << std::endl; + } +} + +////////////////////////////////////////////////////////////////////////////// +// Test3: polymorphism & friend +// +// Note that friendship is not necessary but make things simpler. + +class Shape { +public: + virtual ~Shape() {} + virtual const char* GetType() const = 0; + virtual void Print(std::ostream& os) const = 0; + +protected: + Shape() {} + Shape(double x, double y) : x_(x), y_(y) {} + + template + friend Archiver& operator&(Archiver& ar, Shape& s); + + double x_, y_; +}; + +template +Archiver& operator&(Archiver& ar, Shape& s) { + ar.Member("x") & s.x_; + ar.Member("y") & s.y_; + return ar; +} + +class Circle : public Shape { +public: + Circle() {} + Circle(double x, double y, double radius) : Shape(x, y), radius_(radius) {} + ~Circle() {} + + const char* GetType() const { return "Circle"; } + + void Print(std::ostream& os) const { + os << "Circle (" << x_ << ", " << y_ << ")" << " radius = " << radius_; + } + +private: + template + friend Archiver& operator&(Archiver& ar, Circle& c); + + double radius_; +}; + +template +Archiver& operator&(Archiver& ar, Circle& c) { + ar & static_cast(c); + ar.Member("radius") & c.radius_; + return ar; +} + +class Box : public Shape { +public: + Box() {} + Box(double x, double y, double width, double height) : Shape(x, y), width_(width), height_(height) {} + ~Box() {} + + const char* GetType() const { return "Box"; } + + void Print(std::ostream& os) const { + os << "Box (" << x_ << ", " << y_ << ")" << " width = " << width_ << " height = " << height_; + } + +private: + template + friend Archiver& operator&(Archiver& ar, Box& b); + + double width_, height_; +}; + +template +Archiver& operator&(Archiver& ar, Box& b) { + ar & static_cast(b); + ar.Member("width") & b.width_; + ar.Member("height") & b.height_; + return ar; +} + +class Canvas { +public: + Canvas() {} + ~Canvas() { Clear(); } + + void Clear() { + for (std::vector::iterator itr = shapes_.begin(); itr != shapes_.end(); ++itr) + delete *itr; + } + + void AddShape(Shape* shape) { shapes_.push_back(shape); } + + void Print(std::ostream& os) { + for (std::vector::iterator itr = shapes_.begin(); itr != shapes_.end(); ++itr) { + (*itr)->Print(os); + std::cout << std::endl; + } + } + +private: + template + friend Archiver& operator&(Archiver& ar, Canvas& c); + + std::vector shapes_; +}; + +template +Archiver& operator&(Archiver& ar, Shape*& shape) { + std::string type = ar.IsReader ? "" : shape->GetType(); + ar.StartObject(); + ar.Member("type") & type; + if (type == "Circle") { + if (ar.IsReader) shape = new Circle; + ar & static_cast(*shape); + } + else if (type == "Box") { + if (ar.IsReader) shape = new Box; + ar & static_cast(*shape); + } + return ar.EndObject(); +} + +template +Archiver& operator&(Archiver& ar, Canvas& c) { + size_t shapeCount = c.shapes_.size(); + ar.StartArray(&shapeCount); + if (ar.IsReader) { + c.Clear(); + c.shapes_.resize(shapeCount); + } + for (size_t i = 0; i < shapeCount; i++) + ar & c.shapes_[i]; + return ar.EndArray(); +} + +void test3() { + std::string json; + + // Serialize + { + Canvas c; + c.AddShape(new Circle(1.0, 2.0, 3.0)); + c.AddShape(new Box(4.0, 5.0, 6.0, 7.0)); + + JsonWriter writer; + writer & c; + json = writer.GetString(); + std::cout << json << std::endl; + } + + // Deserialize + { + Canvas c; + JsonReader reader(json.c_str()); + reader & c; + c.Print(std::cout); + } +} + +////////////////////////////////////////////////////////////////////////////// + +int main() { + test1(); + test2(); + test3(); +}