/[svn]/libgig/trunk/src/Serialization.h
ViewVC logotype

Contents of /libgig/trunk/src/Serialization.h

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3142 - (show annotations) (download) (as text)
Wed May 3 17:37:33 2017 UTC (6 years, 10 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 26645 byte(s)
- Serialization.h: Just Doxygen fixes.

1 /***************************************************************************
2 * *
3 * Copyright (C) 2017 Christian Schoenebeck *
4 * <cuse@users.sourceforge.net> *
5 * *
6 * This library is part of libgig. *
7 * *
8 * This library is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This library is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this library; if not, write to the Free Software *
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
21 * MA 02111-1307 USA *
22 ***************************************************************************/
23
24 #ifndef LIBGIG_SERIALIZATION_H
25 #define LIBGIG_SERIALIZATION_H
26
27 #ifdef HAVE_CONFIG_H
28 # include <config.h>
29 #endif
30
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <typeinfo>
34 #include <string>
35 #include <vector>
36 #include <map>
37
38 /** @brief Serialization / deserialization framework.
39 *
40 * See class Archive as starting point for how to implement serialization and
41 * deserialization with your application.
42 *
43 * The classes in this namespace allow to serialize and deserialize native
44 * C++ objects in a portable, easy and flexible way. Serialization is a
45 * technique that allows to transform the current state and data of native
46 * (in this case C++) objects into a data stream (including all other objects
47 * the "serialized" objects relate to); the data stream may then be sent over
48 * "wire" (for example via network connection to another computer, which might
49 * also have a different OS, CPU architecture, native memory word size and
50 * endian type); and finally the data stream would be "deserialized" on that
51 * receiver side, that is transformed again to modify all objects and data
52 * structures on receiver side to resemble the objects' state and data as it
53 * was originally on sender side.
54 *
55 * In contrast to many other already existing serialization frameworks, this
56 * implementation has a strong focus on robustness regarding long-term changes
57 * to the serialized C++ classes of the serialized objects. So even if sender
58 * and receiver are using different versions of their serialized/deserialized
59 * C++ classes, structures and data types (thus having different data structure
60 * layout to a certain extent), this framework aims trying to automatically
61 * adapt its serialization and deserialization process in that case so that
62 * the deserialized objects on receiver side would still reflect the overall
63 * expected states and overall data as intended by the sender. For being able to
64 * do so, this framework stores all kind of additional information about each
65 * serialized object and each data structure member (for example name of each
66 * data structure member, but also the offset of each member within its
67 * containing data structure, precise data types, and more).
68 *
69 * Like most other serialization frameworks, this frameworks does not require a
70 * tree-structured layout of the serialized data structures. So it automatically
71 * handles also cyclic dependencies between serialized data structures
72 * correctly, without i.e. causing endless recursion or redundancy.
73 *
74 * Additionally this framework also allows partial deserialization. Which means
75 * the receiver side may for example decide that it wants to restrict
76 * deserialization so that it would only modify certain objects or certain
77 * members by the deserialization process, leaving all other ones untouched.
78 * So this partial deserialization technique for example allows to implement
79 * flexible preset features for applications in a powerful and easy way.
80 */
81 namespace Serialization {
82
83 class Archive;
84 class Exception;
85
86 typedef std::string String;
87
88 typedef std::vector<uint8_t> RawData;
89
90 typedef void* ID;
91
92 typedef uint32_t Version;
93
94 enum operation_t {
95 OPERATION_NONE,
96 OPERATION_SERIALIZE,
97 OPERATION_DESERIALIZE
98 };
99
100 template<typename T>
101 bool IsEnum(const T& data) {
102 return __is_enum(T);
103 }
104
105 template<typename T>
106 bool IsUnion(const T& data) {
107 return __is_union(T);
108 }
109
110 template<typename T>
111 bool IsClass(const T& data) {
112 return __is_class(T);
113 }
114
115 /*template<typename T>
116 bool IsTrivial(T data) {
117 return __is_trivial(T);
118 }*/
119
120 /*template<typename T>
121 bool IsPOD(T data) {
122 return __is_pod(T);
123 }*/
124
125 /** @brief Unique identifier for one specific C++ object, member or fundamental variable.
126 *
127 * Reflects a unique identifier for one specific serialized C++ class
128 * instance, struct instance, member, primitive pointer, or fundamental
129 * variables.
130 */
131 class UID {
132 public:
133 ID id;
134 size_t size;
135
136 bool isValid() const;
137 operator bool() const { return isValid(); }
138 //bool operator()() const { return isValid(); }
139 bool operator==(const UID& other) const { return id == other.id && size == other.size; }
140 bool operator!=(const UID& other) const { return id != other.id || size != other.size; }
141 bool operator<(const UID& other) const { return id < other.id || (id == other.id && size < other.size); }
142 bool operator>(const UID& other) const { return id > other.id || (id == other.id && size > other.size); }
143
144 template<typename T>
145 static UID from(const T& obj) {
146 return Resolver<T>::resolve(obj);
147 }
148
149 protected:
150 // UID resolver for non-pointer types
151 template<typename T>
152 struct Resolver {
153 static UID resolve(const T& obj) {
154 return (UID) { (ID) &obj, sizeof(obj) };
155 }
156 };
157
158 // UID resolver for pointer types (of 1st degree)
159 template<typename T>
160 struct Resolver<T*> {
161 static UID resolve(const T* const & obj) {
162 return (UID) { (ID) obj, sizeof(*obj) };
163 }
164 };
165 };
166
167 /**
168 * Reflects an invalid UID and behaves similar to NULL as invalid value for
169 * pointer types.
170 */
171 extern const UID NO_UID;
172
173 typedef std::vector<UID> UIDChain;
174
175 /** @brief Abstract reflection of a native C++ data type.
176 *
177 * Provides detailed information about a C++ data type, whether it is a
178 * fundamental C/C++ data type (like int, float, char, etc.) or custom
179 * defined data type like a C++ class, struct, enum, as well as other
180 * features of the data type like its native memory size and more.
181 */
182 class DataType {
183 public:
184 DataType();
185 size_t size() const { return m_size; }
186 bool isValid() const;
187 bool isPointer() const;
188 bool isClass() const;
189 bool isPrimitive() const;
190 bool isInteger() const;
191 bool isReal() const;
192 bool isBool() const;
193 bool isEnum() const;
194 bool isSigned() const;
195 operator bool() const { return isValid(); }
196 //bool operator()() const { return isValid(); }
197 bool operator==(const DataType& other) const;
198 bool operator!=(const DataType& other) const;
199 bool operator<(const DataType& other) const;
200 bool operator>(const DataType& other) const;
201 String asLongDescr() const;
202 String baseTypeName() const { return m_baseTypeName; }
203 String customTypeName() const { return m_customTypeName; }
204
205 template<typename T>
206 static DataType dataTypeOf(const T& data) {
207 return Resolver<T>::resolve(data);
208 }
209
210 protected:
211 DataType(bool isPointer, int size, String baseType, String customType = "");
212
213 template<typename T, bool T_isPointer>
214 struct ResolverBase {
215 static DataType resolve(const T& data) {
216 const std::type_info& type = typeid(data);
217 const int sz = sizeof(data);
218
219 // for primitive types we are using our own type names instead of
220 // using std:::type_info::name(), because the precise output of the
221 // latter may vary between compilers
222 if (type == typeid(int8_t)) return DataType(T_isPointer, sz, "int8");
223 if (type == typeid(uint8_t)) return DataType(T_isPointer, sz, "uint8");
224 if (type == typeid(int16_t)) return DataType(T_isPointer, sz, "int16");
225 if (type == typeid(uint16_t)) return DataType(T_isPointer, sz, "uint16");
226 if (type == typeid(int32_t)) return DataType(T_isPointer, sz, "int32");
227 if (type == typeid(uint32_t)) return DataType(T_isPointer, sz, "uint32");
228 if (type == typeid(int64_t)) return DataType(T_isPointer, sz, "int64");
229 if (type == typeid(uint64_t)) return DataType(T_isPointer, sz, "uint64");
230 if (type == typeid(bool)) return DataType(T_isPointer, sz, "bool");
231 if (type == typeid(float)) return DataType(T_isPointer, sz, "real32");
232 if (type == typeid(double)) return DataType(T_isPointer, sz, "real64");
233
234 if (IsEnum(data)) return DataType(T_isPointer, sz, "enum", rawCppTypeNameOf(data));
235 if (IsUnion(data)) return DataType(T_isPointer, sz, "union", rawCppTypeNameOf(data));
236 if (IsClass(data)) return DataType(T_isPointer, sz, "class", rawCppTypeNameOf(data));
237
238 return DataType();
239 }
240 };
241
242 // DataType resolver for non-pointer types
243 template<typename T>
244 struct Resolver : ResolverBase<T,false> {
245 static DataType resolve(const T& data) {
246 return ResolverBase<T,false>::resolve(data);
247 }
248 };
249
250 // DataType resolver for pointer types (of 1st degree)
251 template<typename T>
252 struct Resolver<T*> : ResolverBase<T,true> {
253 static DataType resolve(const T*& data) {
254 return ResolverBase<T,true>::resolve(*data);
255 }
256 };
257
258 template<typename T>
259 static String rawCppTypeNameOf(const T& data) {
260 #if defined _MSC_VER // Microsoft compiler ...
261 # warning type_info::raw_name() demangling has not been tested yet with Microsoft compiler! Feedback appreciated!
262 String name = typeid(data).raw_name(); //NOTE: I haven't checked yet what MSC actually outputs here exactly
263 #else // i.e. especially GCC and clang ...
264 String name = typeid(data).name();
265 #endif
266 //while (!name.empty() && name[0] >= 0 && name[0] <= 9)
267 // name = name.substr(1);
268 return name;
269 }
270
271 private:
272 String m_baseTypeName;
273 String m_customTypeName;
274 int m_size;
275 bool m_isPointer;
276
277 friend DataType _popDataTypeBlob(const char*& p, const char* end);
278 };
279
280 /** @brief Abstract reflection of a native C++ class/struct's member variable.
281 *
282 * Provides detailed information about a specific C++ member variable of
283 * serialized C++ object, like its C++ data type, offset of this member
284 * within its containing data structure/class, its C++ member variable name
285 * and more.
286 */
287 class Member {
288 public:
289 Member();
290 UID uid() const { return m_uid; }
291 String name() const { return m_name; }
292 size_t offset() const { return m_offset; }
293 const DataType& type() const { return m_type; }
294 bool isValid() const;
295 operator bool() const { return isValid(); }
296 //bool operator()() const { return isValid(); }
297 bool operator==(const Member& other) const;
298 bool operator!=(const Member& other) const;
299 bool operator<(const Member& other) const;
300 bool operator>(const Member& other) const;
301
302 protected:
303 Member(String name, UID uid, size_t offset, DataType type);
304 friend class Archive;
305
306 private:
307 UID m_uid;
308 size_t m_offset;
309 String m_name;
310 DataType m_type;
311
312 friend Member _popMemberBlob(const char*& p, const char* end);
313 };
314
315 /** @brief Abstract reflection of a native C++ class/struct instance.
316 *
317 * Provides detailed information about a specific serialized C++ object,
318 * like its C++ member variables, its C++ class/struct name, its native
319 * memory size and more.
320 */
321 class Object {
322 public:
323 Object();
324 Object(UIDChain uidChain, DataType type);
325
326 UID uid(int index = 0) const {
327 return (index < m_uid.size()) ? m_uid[index] : NO_UID;
328 }
329
330 const UIDChain& uidChain() const { return m_uid; }
331 const DataType& type() const { return m_type; }
332 const RawData& rawData() const { return m_data; }
333
334 Version version() const { return m_version; }
335
336 void setVersion(Version v) {
337 m_version = v;
338 }
339
340 Version minVersion() const { return m_minVersion; }
341
342 void setMinVersion(Version v) {
343 m_minVersion = v;
344 }
345
346 bool isVersionCompatibleTo(const Object& other) const;
347
348 std::vector<Member>& members() { return m_members; }
349 const std::vector<Member>& members() const { return m_members; }
350 Member memberNamed(String name) const;
351 void remove(const Member& member);
352 std::vector<Member> membersOfType(const DataType& type) const;
353 int sequenceIndexOf(const Member& member) const;
354 bool isValid() const;
355 operator bool() const { return isValid(); }
356 //bool operator()() const { return isValid(); }
357 bool operator==(const Object& other) const;
358 bool operator!=(const Object& other) const;
359 bool operator<(const Object& other) const;
360 bool operator>(const Object& other) const;
361
362 private:
363 DataType m_type;
364 UIDChain m_uid;
365 Version m_version;
366 Version m_minVersion;
367 RawData m_data;
368 std::vector<Member> m_members;
369
370 friend Object _popObjectBlob(const char*& p, const char* end);
371 friend void _popPrimitiveValue(const char*& p, const char* end, Object& obj);
372 };
373
374 /** @brief Destination container for serialization, and source container for deserialization.
375 *
376 * This is the main class for implementing serialization and deserialization
377 * with your C++ application. This framework does not require a a tree
378 * structured layout of your C++ objects being serialized/deserialized, it
379 * uses a concept of a "root" object though. So to start serialization
380 * construct an empty Archive object and then instruct it to serialize your
381 * C++ objects by pointing it to your "root" object:
382 * @code
383 * Archive a;
384 * a.serialize(&myRootObject);
385 * @endcode
386 * Or if you prefer the look of operator based code:
387 * @code
388 * Archive a;
389 * a << myRootObject;
390 * @endcode
391 * The Archive object will then serialize all members of the passed C++
392 * object, and will recursively serialize all other C++ objects which it
393 * contains or points to. So the root object is the starting point for the
394 * overall serialization. After the serialize() method returned, you can
395 * then access the serialized data stream by calling rawData() and send that
396 * data stream over "wire", or store it on disk or whatever you may intend
397 * to do with it.
398 *
399 * Then on receiver side likewise, you create a new Archive object, pass the
400 * received data stream i.e. via constructor to the Archive object and call
401 * deserialize() by pointing it to the root object on receiver side:
402 * @code
403 * Archive a(rawDataStream);
404 * a.deserialize(&myRootObject);
405 * @endcode
406 * Or with operator instead:
407 * @code
408 * Archive a(rawDataStream);
409 * a >> myRootObject;
410 * @endcode
411 * Now this framework automatically handles serialization and
412 * deserialization of fundamental data types automatically for you (like
413 * i.e. char, int, long int, float, double, etc.). However for your own
414 * custom C++ classes and structs you must implement one method which
415 * defines which members of your class should actually be serialized and
416 * deserialized. That method to be added must have the following signature:
417 * @code
418 * void serialize(Serialization::Archive* archive);
419 * @endcode
420 * So let's say you have the following simple data structures:
421 * @code
422 * struct Foo {
423 * int a;
424 * bool b;
425 * double c;
426 * };
427 *
428 * struct Bar {
429 * char one;
430 * float two;
431 * Foo foo1;
432 * Foo* pFoo2;
433 * Foo* pFoo3DontTouchMe; // shall not be serialized/deserialized
434 * };
435 * @endcode
436 * So in order to be able to serialize and deserialize objects of those two
437 * structures you would first add the mentioned method to each struct
438 * definition (i.e. in your header file):
439 * @code
440 * struct Foo {
441 * int a;
442 * bool b;
443 * double c;
444 *
445 * void serialize(Serialization::Archive* archive);
446 * };
447 *
448 * struct Bar {
449 * char one;
450 * float two;
451 * Foo foo1;
452 * Foo* pFoo2;
453 * Foo* pFoo3DontTouchMe; // shall not be serialized/deserialized
454 *
455 * void serialize(Serialization::Archive* archive);
456 * };
457 * @endcode
458 * And then you would implement those two new methods like this (i.e. in
459 * your .cpp file):
460 * @code
461 * #define SRLZ(member) \
462 * archive->serializeMember(*this, member, #member);
463 *
464 * void Foo::serialize(Serialization::Archive* archive) {
465 * SRLZ(a);
466 * SRLZ(b);
467 * SRLZ(c);
468 * }
469 *
470 * void Bar::serialize(Serialization::Archive* archive) {
471 * SRLZ(one);
472 * SRLZ(two);
473 * SRLZ(foo1);
474 * SRLZ(pFoo2);
475 * // leaving out pFoo3DontTouchMe here
476 * }
477 * @endcode
478 * Now when you serialize such a Bar object, this framework will also
479 * automatically serialize the respective Foo object(s) accordingly, also
480 * for the pFoo2 pointer for instance (as long as it is not a NULL pointer
481 * that is).
482 *
483 * Note that there is only one method that you need to implement. So the
484 * respective serialize() method implementation of your classes/structs are
485 * both called for serialization, as well as for deserialization!
486 */
487 class Archive {
488 public:
489 Archive();
490 Archive(const RawData& data);
491 Archive(const uint8_t* data, size_t size);
492 virtual ~Archive();
493
494 template<typename T>
495 void serialize(const T* obj) {
496 m_operation = OPERATION_SERIALIZE;
497 m_allObjects.clear();
498 m_rawData.clear();
499 m_root = UID::from(obj);
500 const_cast<T*>(obj)->serialize(this);
501 encode();
502 m_operation = OPERATION_NONE;
503 }
504
505 template<typename T>
506 void deserialize(T* obj) {
507 Archive a;
508 m_operation = OPERATION_DESERIALIZE;
509 obj->serialize(&a);
510 a.m_root = UID::from(obj);
511 Syncer s(a, *this);
512 m_operation = OPERATION_NONE;
513 }
514
515 template<typename T>
516 void operator<<(const T& obj) {
517 serialize(&obj);
518 }
519
520 template<typename T>
521 void operator>>(T& obj) {
522 deserialize(&obj);
523 }
524
525 const RawData& rawData() const { return m_rawData; }
526 virtual String rawDataFormat() const;
527
528 template<typename T_classType, typename T_memberType>
529 void serializeMember(const T_classType& nativeObject, const T_memberType& nativeMember, const char* memberName) {
530 const size_t offset =
531 ((const uint8_t*)(const void*)&nativeMember) -
532 ((const uint8_t*)(const void*)&nativeObject);
533 const UIDChain uids = UIDChainResolver<T_memberType>(nativeMember);
534 const DataType type = DataType::dataTypeOf(nativeMember);
535 const Member member(memberName, uids[0], offset, type);
536 const UID parentUID = UID::from(nativeObject);
537 Object& parent = m_allObjects[parentUID];
538 if (!parent) {
539 const UIDChain uids = UIDChainResolver<T_classType>(nativeObject);
540 const DataType type = DataType::dataTypeOf(nativeObject);
541 parent = Object(uids, type);
542 }
543 parent.members().push_back(member);
544 const Object obj(uids, type);
545 const bool bExistsAlready = m_allObjects.count(uids[0]);
546 const bool isValidObject = obj;
547 const bool bExistingObjectIsInvalid = !m_allObjects[uids[0]];
548 if (!bExistsAlready || (bExistingObjectIsInvalid && isValidObject)) {
549 m_allObjects[uids[0]] = obj;
550 // recurse serialization for all members of this member
551 // (only for struct/class types, noop for primitive types)
552 SerializationRecursion<T_memberType>::serializeObject(this, nativeMember);
553 }
554 }
555
556 virtual void decode(const RawData& data);
557 virtual void decode(const uint8_t* data, size_t size);
558 void clear();
559 void remove(const Object& obj);
560 Object& rootObject();
561 Object& objectByUID(const UID& uid);
562
563 protected:
564 // UID resolver for non-pointer types
565 template<typename T>
566 class UIDChainResolver {
567 public:
568 UIDChainResolver(const T& data) {
569 m_uid.push_back(UID::from(data));
570 }
571
572 operator UIDChain() const { return m_uid; }
573 UIDChain operator()() const { return m_uid; }
574 private:
575 UIDChain m_uid;
576 };
577
578 // UID resolver for pointer types (of 1st degree)
579 template<typename T>
580 class UIDChainResolver<T*> {
581 public:
582 UIDChainResolver(const T*& data) {
583 m_uid.push_back((UID) { &data, sizeof(data) });
584 m_uid.push_back((UID) { data, sizeof(*data) });
585 }
586
587 operator UIDChain() const { return m_uid; }
588 UIDChain operator()() const { return m_uid; }
589 private:
590 UIDChain m_uid;
591 };
592
593 // SerializationRecursion for non-pointer class/struct types.
594 template<typename T, bool T_isRecursive>
595 struct SerializationRecursionImpl {
596 static void serializeObject(Archive* archive, const T& obj) {
597 const_cast<T&>(obj).serialize(archive);
598 }
599 };
600
601 // SerializationRecursion for pointers (of 1st degree) to class/structs.
602 template<typename T, bool T_isRecursive>
603 struct SerializationRecursionImpl<T*,T_isRecursive> {
604 static void serializeObject(Archive* archive, const T*& obj) {
605 if (!obj) return;
606 const_cast<T*&>(obj)->serialize(archive);
607 }
608 };
609
610 // NOOP SerializationRecursion for primitive types.
611 template<typename T>
612 struct SerializationRecursionImpl<T,false> {
613 static void serializeObject(Archive* archive, const T& obj) {}
614 };
615
616 // NOOP SerializationRecursion for pointers (of 1st degree) to primitive types.
617 template<typename T>
618 struct SerializationRecursionImpl<T*,false> {
619 static void serializeObject(Archive* archive, const T*& obj) {}
620 };
621
622 // Automatically handles recursion for class/struct types, while ignoring all primitive types.
623 template<typename T>
624 struct SerializationRecursion : SerializationRecursionImpl<T, __is_class(T)> {
625 };
626
627 class ObjectPool : public std::map<UID,Object> {
628 public:
629 // prevent passing obvious invalid UID values from creating a new pair entry
630 Object& operator[](const UID& k) {
631 static Object invalid;
632 if (!k.isValid()) {
633 invalid = Object();
634 return invalid;
635 }
636 return std::map<UID,Object>::operator[](k);
637 }
638 };
639
640 friend String _encode(const ObjectPool& objects);
641
642 private:
643 String _encodeRootBlob();
644 void _popRootBlob(const char*& p, const char* end);
645 void _popObjectsBlob(const char*& p, const char* end);
646
647 protected:
648 class Syncer {
649 public:
650 Syncer(Archive& dst, Archive& src);
651 protected:
652 void syncObject(const Object& dst, const Object& src);
653 void syncPrimitive(const Object& dst, const Object& src);
654 void syncPointer(const Object& dst, const Object& src);
655 void syncMember(const Member& dstMember, const Member& srcMember);
656 static Member dstMemberMatching(const Object& dstObj, const Object& srcObj, const Member& srcMember);
657 private:
658 Archive& m_dst;
659 Archive& m_src;
660 };
661
662 virtual void encode();
663
664 ObjectPool m_allObjects;
665 operation_t m_operation;
666 UID m_root;
667 RawData m_rawData;
668 };
669
670 /**
671 * Will be thrown whenever an error occurs during an serialization or
672 * deserialization process.
673 */
674 class Exception {
675 public:
676 String Message;
677
678 Exception(String Message) { Exception::Message = Message; }
679 void PrintMessage();
680 virtual ~Exception() {}
681 };
682
683 } // namespace Serialization
684
685 #endif // LIBGIG_SERIALIZATION_H

  ViewVC Help
Powered by ViewVC