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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3185 - (show annotations) (download) (as text)
Wed May 17 15:42:58 2017 UTC (6 years, 11 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 51712 byte(s)
* Serialization: Handle human readable boolean text representations like
  "yes", "no", "true", "false" in Archive::setAutoValue() as expected.
* Bumped version (4.0.0.svn24).

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 #include <time.h>
38
39 #ifndef __has_extension
40 # define __has_extension(x) 0
41 #endif
42
43 #ifndef HAS_BUILTIN_TYPE_TRAITS
44 # if __cplusplus >= 201103L
45 # define HAS_BUILTIN_TYPE_TRAITS 1
46 # elif ( __has_extension(is_class) && __has_extension(is_enum) )
47 # define HAS_BUILTIN_TYPE_TRAITS 1
48 # elif ( __GNUC__ > 4 || ( __GNUC__ == 4 && __GNUC_MINOR__ >= 3 ) )
49 # define HAS_BUILTIN_TYPE_TRAITS 1
50 # elif _MSC_VER >= 1400 /* MS Visual C++ 8.0 (Visual Studio 2005) */
51 # define HAS_BUILTIN_TYPE_TRAITS 1
52 # elif __INTEL_COMPILER >= 1100
53 # define HAS_BUILTIN_TYPE_TRAITS 1
54 # else
55 # define HAS_BUILTIN_TYPE_TRAITS 0
56 # endif
57 #endif
58
59 #if !HAS_BUILTIN_TYPE_TRAITS
60 # include <tr1/type_traits>
61 # define LIBGIG_IS_CLASS(type) std::tr1::__is_union_or_class<type>::value //NOTE: without compiler support we cannot distinguish union from class
62 #else
63 # define LIBGIG_IS_CLASS(type) __is_class(type)
64 #endif
65
66 /** @brief Serialization / deserialization framework.
67 *
68 * See class Archive as starting point for how to implement serialization and
69 * deserialization with your application.
70 *
71 * The classes in this namespace allow to serialize and deserialize native
72 * C++ objects in a portable, easy and flexible way. Serialization is a
73 * technique that allows to transform the current state and data of native
74 * (in this case C++) objects into a data stream (including all other objects
75 * the "serialized" objects relate to); the data stream may then be sent over
76 * "wire" (for example via network connection to another computer, which might
77 * also have a different OS, CPU architecture, native memory word size and
78 * endian type); and finally the data stream would be "deserialized" on that
79 * receiver side, that is transformed again to modify all objects and data
80 * structures on receiver side to resemble the objects' state and data as it
81 * was originally on sender side.
82 *
83 * In contrast to many other already existing serialization frameworks, this
84 * implementation has a strong focus on robustness regarding long-term changes
85 * to the serialized C++ classes of the serialized objects. So even if sender
86 * and receiver are using different versions of their serialized/deserialized
87 * C++ classes, structures and data types (thus having different data structure
88 * layout to a certain extent), this framework aims trying to automatically
89 * adapt its serialization and deserialization process in that case so that
90 * the deserialized objects on receiver side would still reflect the overall
91 * expected states and overall data as intended by the sender. For being able to
92 * do so, this framework stores all kind of additional information about each
93 * serialized object and each data structure member (for example name of each
94 * data structure member, but also the offset of each member within its
95 * containing data structure, precise data types, and more).
96 *
97 * Like most other serialization frameworks, this frameworks does not require a
98 * tree-structured layout of the serialized data structures. So it automatically
99 * handles also cyclic dependencies between serialized data structures
100 * correctly, without i.e. causing endless recursion or redundancy.
101 *
102 * Additionally this framework also allows partial deserialization. Which means
103 * the receiver side may for example decide that it wants to restrict
104 * deserialization so that it would only modify certain objects or certain
105 * members by the deserialization process, leaving all other ones untouched.
106 * So this partial deserialization technique for example allows to implement
107 * flexible preset features for applications in a powerful and easy way.
108 */
109 namespace Serialization {
110
111 // just symbol prototyping
112 class DataType;
113 class Object;
114 class Member;
115 class Archive;
116 class ObjectPool;
117 class Exception;
118
119 typedef std::string String;
120
121 /** @brief Raw data stream of serialized C++ objects.
122 *
123 * This data type is used for the data stream as a result of serializing
124 * your C++ objects with Archive::serialize(), and for native raw data
125 * representation of individual serialized C/C++ objects, members and variables.
126 *
127 * @see Archive::rawData(), Object::rawData()
128 */
129 typedef std::vector<uint8_t> RawData;
130
131 /** @brief Abstract identifier for serialized C++ objects.
132 *
133 * This data type is used for identifying serialized C++ objects and members
134 * of your C++ objects. It is important to know that such an ID might not
135 * necessarily be unique. For example the ID of one C++ object might often
136 * be identical to the ID of the first member of that particular C++ object.
137 * That's why there is additionally the concept of an UID in this framework.
138 *
139 * @see UID
140 */
141 typedef void* ID;
142
143 /** @brief Version number data type.
144 *
145 * This data type is used for maintaining version number information of
146 * your C++ class implementations.
147 *
148 * @see Archive::setVersion() and Archive::setMinVersion()
149 */
150 typedef uint32_t Version;
151
152 /** @brief To which time zone a certain timing information relates to.
153 *
154 * The constants in this enum type are used to define to which precise time
155 * zone a time stamp relates to.
156 */
157 enum time_base_t {
158 LOCAL_TIME, ///< The time stamp relates to the machine's local time zone. Request a time stamp in local time if you want to present that time stamp to the end user.
159 UTC_TIME ///< The time stamp relates to "Greenwhich Mean Time" zone, also known as "Coordinated Universal Time". Request time stamp with UTC if you want to compare that time stamp with other time stamps.
160 };
161
162 /** @brief Check whether data is a C/C++ @c enum type.
163 *
164 * Returns true if the supplied C++ variable or object is of a C/C++ @c enum
165 * type.
166 *
167 * @param data - the variable or object whose data type shall be checked
168 */
169 template<typename T>
170 bool IsEnum(const T& data) {
171 #if !HAS_BUILTIN_TYPE_TRAITS
172 return std::tr1::is_enum<T>::value;
173 #else
174 return __is_enum(T);
175 #endif
176 }
177
178 /** @brief Check whether data is a C++ @c union type.
179 *
180 * Returns true if the supplied C++ variable or object is of a C/C++ @c union
181 * type. Note that the result of this function is only reliable if the C++
182 * compiler you are using has support for built-in type traits. If your C++
183 * compiler does not have built-in type traits support, then this function
184 * will simply return @c false on all your calls.
185 *
186 * @param data - the variable or object whose data type shall be checked
187 */
188 template<typename T>
189 bool IsUnion(const T& data) {
190 #if !HAS_BUILTIN_TYPE_TRAITS
191 return false; // without compiler support we cannot distinguish union from class
192 #else
193 return __is_union(T);
194 #endif
195 }
196
197 /** @brief Check whether data is a C/C++ @c struct or C++ @c class type.
198 *
199 * Returns true if the supplied C++ variable or object is of C/C++ @c struct
200 * or C++ @c class type. Note that if you are using a C++ compiler which
201 * does have built-in type traits support, then this function will also
202 * return @c true on C/C++ @c union types.
203 *
204 * @param data - the variable or object whose data type shall be checked
205 */
206 template<typename T>
207 bool IsClass(const T& data) {
208 #if !HAS_BUILTIN_TYPE_TRAITS
209 return std::tr1::__is_union_or_class<T>::value; // without compiler support we cannot distinguish union from class
210 #else
211 return __is_class(T);
212 #endif
213 }
214
215 /*template<typename T>
216 bool IsTrivial(T data) {
217 return __is_trivial(T);
218 }*/
219
220 /*template<typename T>
221 bool IsPOD(T data) {
222 return __is_pod(T);
223 }*/
224
225 /** @brief Unique identifier referring to one specific native C++ object, member, fundamental variable, or any other native C++ data.
226 *
227 * Reflects a unique identifier for one specific serialized C++ data, i.e.
228 * C++ class instance, C/C++ struct instance, member, primitive pointer,
229 * fundamental variables, or any other native C/C++ data originally being
230 * serialized.
231 *
232 * A unique identifier is composed of an id (an identifier which is not
233 * necessarily unique) and a size. Since the underlying ID is derived from
234 * the original C++ object's memory location, such an ID is not sufficient
235 * to distinguish a particular C++ object from the first member of that C++
236 * object, since both typically share the same memory address. So
237 * additionally the memory size of the respective object or member is
238 * bundled with UID objects to make them unique and distinguishable.
239 */
240 class UID {
241 public:
242 ID id; ///< Abstract non-unique ID of the object or member in question.
243 size_t size; ///< Memory size of the object or member in question.
244
245 bool isValid() const;
246 operator bool() const { return isValid(); } ///< Same as calling isValid().
247 //bool operator()() const { return isValid(); }
248 bool operator==(const UID& other) const { return id == other.id && size == other.size; }
249 bool operator!=(const UID& other) const { return id != other.id || size != other.size; }
250 bool operator<(const UID& other) const { return id < other.id || (id == other.id && size < other.size); }
251 bool operator>(const UID& other) const { return id > other.id || (id == other.id && size > other.size); }
252
253 /** @brief Create an unique indentifier for a native C++ object/member/variable.
254 *
255 * Creates and returns an unique identifier for the passed native C++
256 * object, object member or variable. For the same C++ object/member/variable
257 * this function will always return the same UID. For all other ones,
258 * this function is guaranteed to return a different UID.
259 */
260 template<typename T>
261 static UID from(const T& obj) {
262 return Resolver<T>::resolve(obj);
263 }
264
265 protected:
266 // UID resolver for non-pointer types
267 template<typename T>
268 struct Resolver {
269 static UID resolve(const T& obj) {
270 const UID uid = { (ID) &obj, sizeof(obj) };
271 return uid;
272 }
273 };
274
275 // UID resolver for pointer types (of 1st degree)
276 template<typename T>
277 struct Resolver<T*> {
278 static UID resolve(const T* const & obj) {
279 const UID uid = { (ID) obj, sizeof(*obj) };
280 return uid;
281 }
282 };
283 };
284
285 /**
286 * Reflects an invalid UID and behaves similar to NULL as invalid value for
287 * pointer types. All UID objects are first initialized with this value,
288 * and it essentially an all zero object.
289 */
290 extern const UID NO_UID;
291
292 /** @brief Chain of UIDs.
293 *
294 * This data type is used for native C++ pointers. The first member of the
295 * UID chain is the unique identifier of the C++ pointer itself, then the
296 * following UIDs are the respective objects or variables the pointer is
297 * pointing to. The size (the amount of elements) of the UIDChain depends
298 * solely on the degree of the pointer type. For example the following C/C++
299 * pointer:
300 * @code
301 * int* pNumber;
302 * @endcode
303 * is an integer pointer of first degree. Such a pointer would have a
304 * UIDChain with 2 members: the first element would be the UID of the
305 * pointer itself, the second element of the chain would be the integer data
306 * that pointer is pointing to. In the following example:
307 * @code
308 * bool*** pppSomeFlag;
309 * @endcode
310 * That boolean pointer would be of third degree, and thus its UIDChain
311 * would have a size of 4 (elements).
312 *
313 * Accordingly a non pointer type like:
314 * @code
315 * float f;
316 * @endcode
317 * would yield in a UIDChain of size 1.
318 *
319 * Since however this serialization framework currently only supports
320 * pointers of first degree yet, all UIDChains are currently either of
321 * size 1 or 2, which might change in future though.
322 */
323 typedef std::vector<UID> UIDChain;
324
325 // prototyping of private internal friend functions
326 static String _encodePrimitiveValue(const Object& obj);
327 static DataType _popDataTypeBlob(const char*& p, const char* end);
328 static Member _popMemberBlob(const char*& p, const char* end);
329 static Object _popObjectBlob(const char*& p, const char* end);
330 static void _popPrimitiveValue(const char*& p, const char* end, Object& obj);
331 static String _primitiveObjectValueToString(const Object& obj);
332 // |
333 template<typename T>
334 static T _primitiveObjectValueToNumber(const Object& obj);
335
336 /** @brief Abstract reflection of a native C++ data type.
337 *
338 * Provides detailed information about a serialized C++ data type, whether
339 * it is a fundamental C/C++ data type (like @c int, @c float, @c char,
340 * etc.) or custom defined data types like a C++ @c class, C/C++ @c struct,
341 * @c enum, as well as other features of the respective data type like its
342 * native memory size and more.
343 *
344 * All informations provided by this class are retrieved from the
345 * respective individual C++ objects, their members and other data when
346 * they are serialized, and all those information are stored with the
347 * serialized archive and its resulting data stream. Due to the availability
348 * of these extensive data type information within serialized archives, this
349 * framework is capable to use them in order to adapt its deserialization
350 * process upon subsequent changes to your individual C++ classes.
351 */
352 class DataType {
353 public:
354 DataType();
355 size_t size() const { return m_size; } ///< Returns native memory size of the respective C++ object or variable.
356 bool isValid() const;
357 bool isPointer() const;
358 bool isClass() const;
359 bool isPrimitive() const;
360 bool isInteger() const;
361 bool isReal() const;
362 bool isBool() const;
363 bool isEnum() const;
364 bool isSigned() const;
365 operator bool() const { return isValid(); } ///< Same as calling isValid().
366 //bool operator()() const { return isValid(); }
367 bool operator==(const DataType& other) const;
368 bool operator!=(const DataType& other) const;
369 bool operator<(const DataType& other) const;
370 bool operator>(const DataType& other) const;
371 String asLongDescr() const;
372 String baseTypeName() const;
373 String customTypeName(bool demangle = false) const;
374
375 /** @brief Construct a DataType object for the given native C++ data.
376 *
377 * Use this function to create corresponding DataType objects for
378 * native C/C++ objects, members and variables.
379 *
380 * @param data - native C/C++ object/member/variable a DataType object
381 * shall be created for
382 * @returns corresponding DataType object for the supplied native C/C++
383 * object/member/variable
384 */
385 template<typename T>
386 static DataType dataTypeOf(const T& data) {
387 return Resolver<T>::resolve(data);
388 }
389
390 protected:
391 DataType(bool isPointer, int size, String baseType, String customType = "");
392
393 template<typename T, bool T_isPointer>
394 struct ResolverBase {
395 static DataType resolve(const T& data) {
396 const std::type_info& type = typeid(data);
397 const int sz = sizeof(data);
398
399 // for primitive types we are using our own type names instead of
400 // using std:::type_info::name(), because the precise output of the
401 // latter may vary between compilers
402 if (type == typeid(int8_t)) return DataType(T_isPointer, sz, "int8");
403 if (type == typeid(uint8_t)) return DataType(T_isPointer, sz, "uint8");
404 if (type == typeid(int16_t)) return DataType(T_isPointer, sz, "int16");
405 if (type == typeid(uint16_t)) return DataType(T_isPointer, sz, "uint16");
406 if (type == typeid(int32_t)) return DataType(T_isPointer, sz, "int32");
407 if (type == typeid(uint32_t)) return DataType(T_isPointer, sz, "uint32");
408 if (type == typeid(int64_t)) return DataType(T_isPointer, sz, "int64");
409 if (type == typeid(uint64_t)) return DataType(T_isPointer, sz, "uint64");
410 if (type == typeid(bool)) return DataType(T_isPointer, sz, "bool");
411 if (type == typeid(float)) return DataType(T_isPointer, sz, "real32");
412 if (type == typeid(double)) return DataType(T_isPointer, sz, "real64");
413
414 if (IsEnum(data)) return DataType(T_isPointer, sz, "enum", rawCppTypeNameOf(data));
415 if (IsUnion(data)) return DataType(T_isPointer, sz, "union", rawCppTypeNameOf(data));
416 if (IsClass(data)) return DataType(T_isPointer, sz, "class", rawCppTypeNameOf(data));
417
418 return DataType();
419 }
420 };
421
422 // DataType resolver for non-pointer types
423 template<typename T>
424 struct Resolver : ResolverBase<T,false> {
425 static DataType resolve(const T& data) {
426 return ResolverBase<T,false>::resolve(data);
427 }
428 };
429
430 // DataType resolver for pointer types (of 1st degree)
431 template<typename T>
432 struct Resolver<T*> : ResolverBase<T,true> {
433 static DataType resolve(const T*& data) {
434 return ResolverBase<T,true>::resolve(*data);
435 }
436 };
437
438 template<typename T>
439 static String rawCppTypeNameOf(const T& data) {
440 #if defined _MSC_VER // Microsoft compiler ...
441 # warning type_info::raw_name() demangling has not been tested yet with Microsoft compiler! Feedback appreciated!
442 String name = typeid(data).raw_name(); //NOTE: I haven't checked yet what MSC actually outputs here exactly
443 #else // i.e. especially GCC and clang ...
444 String name = typeid(data).name();
445 #endif
446 //while (!name.empty() && name[0] >= 0 && name[0] <= 9)
447 // name = name.substr(1);
448 return name;
449 }
450
451 private:
452 String m_baseTypeName;
453 String m_customTypeName;
454 int m_size;
455 bool m_isPointer;
456
457 friend DataType _popDataTypeBlob(const char*& p, const char* end);
458 friend class Archive;
459 };
460
461 /** @brief Abstract reflection of a native C++ class/struct's member variable.
462 *
463 * Provides detailed information about a specific C++ member variable of
464 * serialized C++ object, like its C++ data type, offset of this member
465 * within its containing data structure/class, its C++ member variable name
466 * and more.
467 *
468 * Consider you defined the following user defined C/C++ @c struct type in
469 * your application:
470 * @code
471 * struct Foo {
472 * int a;
473 * bool b;
474 * double someValue;
475 * };
476 * @endcode
477 * Then @c a, @c b and @c someValue are "members" of @c struct @c Foo for
478 * instance. So that @c struct would have 3 members in the latter example.
479 *
480 * @see Object::members()
481 */
482 class Member {
483 public:
484 Member();
485 UID uid() const;
486 String name() const;
487 size_t offset() const;
488 const DataType& type() const;
489 bool isValid() const;
490 operator bool() const { return isValid(); } ///< Same as calling isValid().
491 //bool operator()() const { return isValid(); }
492 bool operator==(const Member& other) const;
493 bool operator!=(const Member& other) const;
494 bool operator<(const Member& other) const;
495 bool operator>(const Member& other) const;
496
497 protected:
498 Member(String name, UID uid, size_t offset, DataType type);
499 friend class Archive;
500
501 private:
502 UID m_uid;
503 size_t m_offset;
504 String m_name;
505 DataType m_type;
506
507 friend Member _popMemberBlob(const char*& p, const char* end);
508 };
509
510 /** @brief Abstract reflection of some native serialized C/C++ data.
511 *
512 * When your native C++ objects are serialized, all native data is
513 * translated and reflected by such an Object reflection. So each instance
514 * of your serialized native C++ class objects become available as an
515 * Object, but also each member variable of your C++ objects is translated
516 * into an Object, and any other native C/C++ data. So essentially every
517 * native data is turned into its own Object and accessible by this API.
518 *
519 * For each one of those Object reflections, this class provides detailed
520 * information about their native origin. For example if an Object
521 * represents a native C++ class instante, then it provides access to its
522 * C++ class/struct name, to its C++ member variables, its native memory
523 * size and much more.
524 *
525 * Even though this framework allows you to adjust abstract Object instances
526 * to a certain extent, most of the methods of this Object class are
527 * read-only though and the actual modifyable methods are made available
528 * not as part of this Object class, but as part of the Archive class
529 * instead. This design decision was made for performance and safety
530 * reasons.
531 *
532 * @see Archive::setIntValue() as an example for modifying Object instances.
533 */
534 class Object {
535 public:
536 Object();
537 Object(UIDChain uidChain, DataType type);
538
539 UID uid(int index = 0) const;
540 const UIDChain& uidChain() const;
541 const DataType& type() const;
542 const RawData& rawData() const;
543 Version version() const;
544 Version minVersion() const;
545 bool isVersionCompatibleTo(const Object& other) const;
546 std::vector<Member>& members();
547 const std::vector<Member>& members() const;
548 Member memberNamed(String name) const;
549 Member memberByUID(const UID& uid) const;
550 std::vector<Member> membersOfType(const DataType& type) const;
551 int sequenceIndexOf(const Member& member) const;
552 bool isValid() const;
553 operator bool() const { return isValid(); } ///< Same as calling isValid().
554 //bool operator()() const { return isValid(); }
555 bool operator==(const Object& other) const;
556 bool operator!=(const Object& other) const;
557 bool operator<(const Object& other) const;
558 bool operator>(const Object& other) const;
559
560 protected:
561 void remove(const Member& member);
562 void setVersion(Version v);
563 void setMinVersion(Version v);
564
565 private:
566 DataType m_type;
567 UIDChain m_uid;
568 Version m_version;
569 Version m_minVersion;
570 RawData m_data;
571 std::vector<Member> m_members;
572
573 friend String _encodePrimitiveValue(const Object& obj);
574 friend Object _popObjectBlob(const char*& p, const char* end);
575 friend void _popPrimitiveValue(const char*& p, const char* end, Object& obj);
576 friend String _primitiveObjectValueToString(const Object& obj);
577
578 template<typename T>
579 friend T _primitiveObjectValueToNumber(const Object& obj);
580
581 friend class Archive;
582 };
583
584 /** @brief Destination container for serialization, and source container for deserialization.
585 *
586 * This is the main class for implementing serialization and deserialization
587 * with your C++ application. This framework does not require a a tree
588 * structured layout of your C++ objects being serialized/deserialized, it
589 * uses a concept of a "root" object though. So to start serialization
590 * construct an empty Archive object and then instruct it to serialize your
591 * C++ objects by pointing it to your "root" object:
592 * @code
593 * Archive a;
594 * a.serialize(&myRootObject);
595 * @endcode
596 * Or if you prefer the look of operator based code:
597 * @code
598 * Archive a;
599 * a << myRootObject;
600 * @endcode
601 * The Archive object will then serialize all members of the passed C++
602 * object, and will recursively serialize all other C++ objects which it
603 * contains or points to. So the root object is the starting point for the
604 * overall serialization. After the serialize() method returned, you can
605 * then access the serialized data stream by calling rawData() and send that
606 * data stream over "wire", or store it on disk or whatever you may intend
607 * to do with it.
608 *
609 * Then on receiver side likewise, you create a new Archive object, pass the
610 * received data stream i.e. via constructor to the Archive object and call
611 * deserialize() by pointing it to the root object on receiver side:
612 * @code
613 * Archive a(rawDataStream);
614 * a.deserialize(&myRootObject);
615 * @endcode
616 * Or with operator instead:
617 * @code
618 * Archive a(rawDataStream);
619 * a >> myRootObject;
620 * @endcode
621 * Now this framework automatically handles serialization and
622 * deserialization of fundamental data types automatically for you (like
623 * i.e. char, int, long int, float, double, etc.). However for your own
624 * custom C++ classes and structs you must implement one method which
625 * defines which members of your class should actually be serialized and
626 * deserialized. That method to be added must have the following signature:
627 * @code
628 * void serialize(Serialization::Archive* archive);
629 * @endcode
630 * So let's say you have the following simple data structures:
631 * @code
632 * struct Foo {
633 * int a;
634 * bool b;
635 * double c;
636 * };
637 *
638 * struct Bar {
639 * char one;
640 * float two;
641 * Foo foo1;
642 * Foo* pFoo2;
643 * Foo* pFoo3DontTouchMe; // shall not be serialized/deserialized
644 * };
645 * @endcode
646 * So in order to be able to serialize and deserialize objects of those two
647 * structures you would first add the mentioned method to each struct
648 * definition (i.e. in your header file):
649 * @code
650 * struct Foo {
651 * int a;
652 * bool b;
653 * double c;
654 *
655 * void serialize(Serialization::Archive* archive);
656 * };
657 *
658 * struct Bar {
659 * char one;
660 * float two;
661 * Foo foo1;
662 * Foo* pFoo2;
663 * Foo* pFoo3DontTouchMe; // shall not be serialized/deserialized
664 *
665 * void serialize(Serialization::Archive* archive);
666 * };
667 * @endcode
668 * And then you would implement those two new methods like this (i.e. in
669 * your .cpp file):
670 * @code
671 * #define SRLZ(member) \
672 * archive->serializeMember(*this, member, #member);
673 *
674 * void Foo::serialize(Serialization::Archive* archive) {
675 * SRLZ(a);
676 * SRLZ(b);
677 * SRLZ(c);
678 * }
679 *
680 * void Bar::serialize(Serialization::Archive* archive) {
681 * SRLZ(one);
682 * SRLZ(two);
683 * SRLZ(foo1);
684 * SRLZ(pFoo2);
685 * // leaving out pFoo3DontTouchMe here
686 * }
687 * @endcode
688 * Now when you serialize such a Bar object, this framework will also
689 * automatically serialize the respective Foo object(s) accordingly, also
690 * for the pFoo2 pointer for instance (as long as it is not a NULL pointer
691 * that is).
692 *
693 * Note that there is only one method that you need to implement. So the
694 * respective serialize() method implementation of your classes/structs are
695 * both called for serialization, as well as for deserialization!
696 *
697 * In case you need to enforce backward incompatiblity for one of your C++
698 * classes, you can do so by setting a version and minimum version for your
699 * class (see @c setVersion() and @c setMinVersion() for details).
700 */
701 class Archive {
702 public:
703 Archive();
704 Archive(const RawData& data);
705 Archive(const uint8_t* data, size_t size);
706 virtual ~Archive();
707
708 /** @brief Initiate serialization.
709 *
710 * Initiates serialization of all native C++ objects, which means
711 * capturing and storing the current data of all your C++ objects as
712 * content of this Archive.
713 *
714 * This framework has a concept of a "root" object which you must pass
715 * to this method. The root object is the starting point for
716 * serialization of your C++ objects. The framework will then
717 * recursively serialize all members of that C++ object an continue to
718 * serialize all other C++ objects that it might contain or point to.
719 *
720 * After this method returned, you might traverse all serialized objects
721 * by walking them starting from the rootObject(). You might then modify
722 * that abstract reflection of your C++ objects and finally you might
723 * call rawData() to get an encoded raw data stream which you might use
724 * for sending it "over wire" to somewhere where it is going to be
725 * deserialized later on.
726 *
727 * Note that whenever you call this method, the previous content of this
728 * Archive will first be cleared.
729 *
730 * @param obj - native C++ root object where serialization shall start
731 * @see Archive::operator<<()
732 */
733 template<typename T>
734 void serialize(const T* obj) {
735 m_operation = OPERATION_SERIALIZE;
736 m_allObjects.clear();
737 m_rawData.clear();
738 m_root = UID::from(obj);
739 const_cast<T*>(obj)->serialize(this);
740 encode();
741 m_operation = OPERATION_NONE;
742 }
743
744 /** @brief Initiate deserialization.
745 *
746 * Initiates deserialization of all native C++ objects, which means all
747 * your C++ objects will be restored with the values contained in this
748 * Archive. So that also means calling deserialize() only makes sense if
749 * this a non-empty Archive, which i.e. is the case if you either called
750 * serialize() with this Archive object before or if you passed a
751 * previously serialized raw data stream to the constructor of this
752 * Archive object.
753 *
754 * This framework has a concept of a "root" object which you must pass
755 * to this method. The root object is the starting point for
756 * deserialization of your C++ objects. The framework will then
757 * recursively deserialize all members of that C++ object an continue to
758 * deserialize all other C++ objects that it might contain or point to,
759 * according to the values stored in this Archive.
760 *
761 * @param obj - native C++ root object where deserialization shall start
762 * @see Archive::operator>>()
763 *
764 * @throws Exception if the data stored in this Archive cannot be
765 * restored to the C++ objects passed to this method, i.e.
766 * because of version or type incompatibilities.
767 */
768 template<typename T>
769 void deserialize(T* obj) {
770 Archive a;
771 m_operation = OPERATION_DESERIALIZE;
772 obj->serialize(&a);
773 a.m_root = UID::from(obj);
774 Syncer s(a, *this);
775 m_operation = OPERATION_NONE;
776 }
777
778 /** @brief Initiate serialization of your C++ objects.
779 *
780 * Same as calling @c serialize(), this is just meant if you prefer
781 * to use operator based code instead, which you might find to be more
782 * intuitive.
783 *
784 * Example:
785 * @code
786 * Archive a;
787 * a << myRootObject;
788 * @endcode
789 *
790 * @see Archive::serialize() for more details.
791 */
792 template<typename T>
793 void operator<<(const T& obj) {
794 serialize(&obj);
795 }
796
797 /** @brief Initiate deserialization of your C++ objects.
798 *
799 * Same as calling @c deserialize(), this is just meant if you prefer
800 * to use operator based code instead, which you might find to be more
801 * intuitive.
802 *
803 * Example:
804 * @code
805 * Archive a(rawDataStream);
806 * a >> myRootObject;
807 * @endcode
808 *
809 * @throws Exception if the data stored in this Archive cannot be
810 * restored to the C++ objects passed to this method, i.e.
811 * because of version or type incompatibilities.
812 *
813 * @see Archive::deserialize() for more details.
814 */
815 template<typename T>
816 void operator>>(T& obj) {
817 deserialize(&obj);
818 }
819
820 const RawData& rawData();
821 virtual String rawDataFormat() const;
822
823 /** @brief Serialize a native C/C++ member variable.
824 *
825 * This method is usually called by the serialize() method
826 * implementation of your C/C++ structs and classes, for each of the
827 * member variables that shall be serialized and deserialized
828 * automatically with this framework. It is recommend that you are not
829 * using this method name directly, but rather define a short hand C
830 * macro in your .cpp file like:
831 * @code
832 * #define SRLZ(member) \
833 * archive->serializeMember(*this, member, #member);
834 *
835 * void Foo::serialize(Serialization::Archive* archive) {
836 * SRLZ(a);
837 * SRLZ(b);
838 * SRLZ(c);
839 * }
840 * @endcode
841 * As you can see, using such a macro makes your code more readable and
842 * less error prone.
843 *
844 * It is completely up to you to decide which ones of your member
845 * variables shall automatically be serialized and deserialized with
846 * this framework. Only those member variables which are registered by
847 * calling this method will be serialized and deserialized. It does not
848 * really matter in which order you register your individiual member
849 * variables by calling this method, but the sequence is actually stored
850 * as meta information with the resulting archive and the resulting raw
851 * data stream. That meta information might then be used by this
852 * framework to automatically correct and adapt deserializing that
853 * archive later on for a future (or older) and potentially heavily
854 * modified version of your software. So it is recommended, even though
855 * also not required, that you may retain the sequence of your
856 * serializeMember() calls for your individual C++ classes' members over
857 * all your software versions, to retain backward compatibility of older
858 * archives as much as possible.
859 *
860 * @param nativeObject - native C++ object to be registered for
861 * serialization / deserialization
862 * @param nativeMember - native C++ member variable of @a nativeObject
863 * to be registered for serialization /
864 * deserialization
865 * @param memberName - name of @a nativeMember to be stored with this
866 * archive
867 */
868 template<typename T_classType, typename T_memberType>
869 void serializeMember(const T_classType& nativeObject, const T_memberType& nativeMember, const char* memberName) {
870 const size_t offset =
871 ((const uint8_t*)(const void*)&nativeMember) -
872 ((const uint8_t*)(const void*)&nativeObject);
873 const UIDChain uids = UIDChainResolver<T_memberType>(nativeMember);
874 const DataType type = DataType::dataTypeOf(nativeMember);
875 const Member member(memberName, uids[0], offset, type);
876 const UID parentUID = UID::from(nativeObject);
877 Object& parent = m_allObjects[parentUID];
878 if (!parent) {
879 const UIDChain uids = UIDChainResolver<T_classType>(nativeObject);
880 const DataType type = DataType::dataTypeOf(nativeObject);
881 parent = Object(uids, type);
882 }
883 parent.members().push_back(member);
884 const Object obj(uids, type);
885 const bool bExistsAlready = m_allObjects.count(uids[0]);
886 const bool isValidObject = obj;
887 const bool bExistingObjectIsInvalid = !m_allObjects[uids[0]];
888 if (!bExistsAlready || (bExistingObjectIsInvalid && isValidObject)) {
889 m_allObjects[uids[0]] = obj;
890 // recurse serialization for all members of this member
891 // (only for struct/class types, noop for primitive types)
892 SerializationRecursion<T_memberType>::serializeObject(this, nativeMember);
893 }
894 }
895
896 /** @brief Set current version number for your C++ class.
897 *
898 * By calling this method you can define a version number for your
899 * current C++ class (that is a version for its current data structure
900 * layout and method implementations) that is going to be stored along
901 * with the serialized archive. Only call this method if you really want
902 * to constrain compatibility of your C++ class.
903 *
904 * Along with calling @c setMinVersion() this provides a way for you
905 * to constrain backward compatibility regarding serialization and
906 * deserialization of your C++ class which the Archive class will obey
907 * to. If required, then typically you might do so in your
908 * @c serialize() method implementation like:
909 * @code
910 * #define SRLZ(member) \
911 * archive->serializeMember(*this, member, #member);
912 *
913 * void Foo::serialize(Serialization::Archive* archive) {
914 * // when serializing: the current version of this class that is
915 * // going to be stored with the serialized archive
916 * archive->setVersion(*this, 6);
917 * // when deserializing: the minimum version this C++ class is
918 * // compatible with
919 * archive->setMinVersion(*this, 3);
920 * // actual data mebers to serialize / deserialize
921 * SRLZ(a);
922 * SRLZ(b);
923 * SRLZ(c);
924 * }
925 * @endcode
926 * In this example above, the C++ class "Foo" would be serialized along
927 * with the version number @c 6 and minimum version @c 3 as additional
928 * meta information in the resulting archive (and its raw data stream
929 * respectively).
930 *
931 * When deserializing archives with the example C++ class code above,
932 * the Archive object would check whether your originally serialized
933 * C++ "Foo" object had at least version number @c 3, if not the
934 * deserialization process would automatically be stopped with a
935 * @c Serialization::Exception, claiming that the classes are version
936 * incompatible.
937 *
938 * But also consider the other way around: you might have serialized
939 * your latest version of your C++ class, and might deserialize that
940 * archive with an older version of your C++ class. In that case it will
941 * likewise be checked whether the version of that old C++ class is at
942 * least as high as the minimum version set with the already seralized
943 * bleeding edge C++ class.
944 *
945 * Since this Serialization / deserialization framework is designed to
946 * be robust on changes to your C++ classes and aims trying to
947 * deserialize all your C++ objects correctly even if your C++ classes
948 * have seen substantial software changes in the meantime; you might
949 * sometimes see it as necessary to constrain backward compatibility
950 * this way. Because obviously there are certain things this framework
951 * can cope with, like for example that you renamed a data member while
952 * keeping the layout consistent, or that you have added new members to
953 * your C++ class or simply changed the order of your members in your
954 * C++ class. But what this framework cannot detect is for example if
955 * you changed the semantics of the values stored with your members, or
956 * even substantially changed the algorithms in your class methods such
957 * that they would not handle the data of your C++ members in the same
958 * and correct way anymore.
959 *
960 * @param nativeObject - your C++ object you want to set a version for
961 * @param v - the version number to set for your C++ class (by default,
962 * that is if you do not explicitly call this method, then
963 * your C++ object will be stored with version number @c 0 ).
964 */
965 template<typename T_classType>
966 void setVersion(const T_classType& nativeObject, Version v) {
967 const UID uid = UID::from(nativeObject);
968 Object& obj = m_allObjects[uid];
969 if (!obj) {
970 const UIDChain uids = UIDChainResolver<T_classType>(nativeObject);
971 const DataType type = DataType::dataTypeOf(nativeObject);
972 obj = Object(uids, type);
973 }
974 setVersion(obj, v);
975 }
976
977 /** @brief Set a minimum version number for your C++ class.
978 *
979 * Call this method to define a minimum version that your current C++
980 * class implementation would be compatible with when it comes to
981 * deserialization of an archive containing an object of your C++ class.
982 * Like the version information, the minimum version will also be stored
983 * for objects of your C++ class with the resulting archive (and its
984 * resulting raw data stream respectively).
985 *
986 * When you start to constrain version compatibility of your C++ class
987 * you usually start by using 1 as version and 1 as minimum version.
988 * So it is eligible to set the same number to both version and minimum
989 * version. However you must @b not set a minimum version higher than
990 * version. Doing so would not raise an exception, but the resulting
991 * behavior would be undefined.
992 *
993 * It is not relevant whether you first set version and then minimum
994 * version or vice versa. It is also not relevant when exactly you set
995 * those two numbers, even though usually you would set both in your
996 * serialize() method implementation.
997 *
998 * @see @c setVersion() for more details about this overall topic.
999 *
1000 * @param nativeObject - your C++ object you want to set a version for
1001 * @param v - the minimum version you want to define for your C++ class
1002 * (by default, that is if you do not explicitly call this
1003 * method, then a minium version of @c 0 is assumed for your
1004 * C++ class instead).
1005 */
1006 template<typename T_classType>
1007 void setMinVersion(const T_classType& nativeObject, Version v) {
1008 const UID uid = UID::from(nativeObject);
1009 Object& obj = m_allObjects[uid];
1010 if (!obj) {
1011 const UIDChain uids = UIDChainResolver<T_classType>(nativeObject);
1012 const DataType type = DataType::dataTypeOf(nativeObject);
1013 obj = Object(uids, type);
1014 }
1015 setMinVersion(obj, v);
1016 }
1017
1018 virtual void decode(const RawData& data);
1019 virtual void decode(const uint8_t* data, size_t size);
1020 void clear();
1021 bool isModified() const;
1022 void removeMember(Object& parent, const Member& member);
1023 void remove(const Object& obj);
1024 Object& rootObject();
1025 Object& objectByUID(const UID& uid);
1026 void setAutoValue(Object& object, String value);
1027 void setIntValue(Object& object, int64_t value);
1028 void setRealValue(Object& object, double value);
1029 void setBoolValue(Object& object, bool value);
1030 void setEnumValue(Object& object, uint64_t value);
1031 String valueAsString(const Object& object);
1032 int64_t valueAsInt(const Object& object);
1033 double valueAsReal(const Object& object);
1034 bool valueAsBool(const Object& object);
1035 void setVersion(Object& object, Version v);
1036 void setMinVersion(Object& object, Version v);
1037 String name() const;
1038 void setName(String name);
1039 String comment() const;
1040 void setComment(String comment);
1041 time_t timeStampCreated() const;
1042 time_t timeStampModified() const;
1043 tm dateTimeCreated(time_base_t base = LOCAL_TIME) const;
1044 tm dateTimeModified(time_base_t base = LOCAL_TIME) const;
1045
1046 protected:
1047 // UID resolver for non-pointer types
1048 template<typename T>
1049 class UIDChainResolver {
1050 public:
1051 UIDChainResolver(const T& data) {
1052 m_uid.push_back(UID::from(data));
1053 }
1054
1055 operator UIDChain() const { return m_uid; }
1056 UIDChain operator()() const { return m_uid; }
1057 private:
1058 UIDChain m_uid;
1059 };
1060
1061 // UID resolver for pointer types (of 1st degree)
1062 template<typename T>
1063 class UIDChainResolver<T*> {
1064 public:
1065 UIDChainResolver(const T*& data) {
1066 const UID uids[2] = {
1067 { &data, sizeof(data) },
1068 { data, sizeof(*data) }
1069 };
1070 m_uid.push_back(uids[0]);
1071 m_uid.push_back(uids[1]);
1072 }
1073
1074 operator UIDChain() const { return m_uid; }
1075 UIDChain operator()() const { return m_uid; }
1076 private:
1077 UIDChain m_uid;
1078 };
1079
1080 // SerializationRecursion for non-pointer class/struct types.
1081 template<typename T, bool T_isRecursive>
1082 struct SerializationRecursionImpl {
1083 static void serializeObject(Archive* archive, const T& obj) {
1084 const_cast<T&>(obj).serialize(archive);
1085 }
1086 };
1087
1088 // SerializationRecursion for pointers (of 1st degree) to class/structs.
1089 template<typename T, bool T_isRecursive>
1090 struct SerializationRecursionImpl<T*,T_isRecursive> {
1091 static void serializeObject(Archive* archive, const T*& obj) {
1092 if (!obj) return;
1093 const_cast<T*&>(obj)->serialize(archive);
1094 }
1095 };
1096
1097 // NOOP SerializationRecursion for primitive types.
1098 template<typename T>
1099 struct SerializationRecursionImpl<T,false> {
1100 static void serializeObject(Archive* archive, const T& obj) {}
1101 };
1102
1103 // NOOP SerializationRecursion for pointers (of 1st degree) to primitive types.
1104 template<typename T>
1105 struct SerializationRecursionImpl<T*,false> {
1106 static void serializeObject(Archive* archive, const T*& obj) {}
1107 };
1108
1109 // Automatically handles recursion for class/struct types, while ignoring all primitive types.
1110 template<typename T>
1111 struct SerializationRecursion : SerializationRecursionImpl<T, LIBGIG_IS_CLASS(T)> {
1112 };
1113
1114 class ObjectPool : public std::map<UID,Object> {
1115 public:
1116 // prevent passing obvious invalid UID values from creating a new pair entry
1117 Object& operator[](const UID& k) {
1118 static Object invalid;
1119 if (!k.isValid()) {
1120 invalid = Object();
1121 return invalid;
1122 }
1123 return std::map<UID,Object>::operator[](k);
1124 }
1125 };
1126
1127 friend String _encode(const ObjectPool& objects);
1128
1129 private:
1130 String _encodeRootBlob();
1131 void _popRootBlob(const char*& p, const char* end);
1132 void _popObjectsBlob(const char*& p, const char* end);
1133
1134 protected:
1135 class Syncer {
1136 public:
1137 Syncer(Archive& dst, Archive& src);
1138 protected:
1139 void syncObject(const Object& dst, const Object& src);
1140 void syncPrimitive(const Object& dst, const Object& src);
1141 void syncPointer(const Object& dst, const Object& src);
1142 void syncMember(const Member& dstMember, const Member& srcMember);
1143 static Member dstMemberMatching(const Object& dstObj, const Object& srcObj, const Member& srcMember);
1144 private:
1145 Archive& m_dst;
1146 Archive& m_src;
1147 };
1148
1149 enum operation_t {
1150 OPERATION_NONE,
1151 OPERATION_SERIALIZE,
1152 OPERATION_DESERIALIZE
1153 };
1154
1155 virtual void encode();
1156
1157 ObjectPool m_allObjects;
1158 operation_t m_operation;
1159 UID m_root;
1160 RawData m_rawData;
1161 bool m_isModified;
1162 String m_name;
1163 String m_comment;
1164 time_t m_timeCreated;
1165 time_t m_timeModified;
1166 };
1167
1168 /**
1169 * Will be thrown whenever an error occurs during an serialization or
1170 * deserialization process.
1171 */
1172 class Exception {
1173 public:
1174 String Message;
1175
1176 Exception(String Message) { Exception::Message = Message; }
1177 void PrintMessage();
1178 virtual ~Exception() {}
1179 };
1180
1181 } // namespace Serialization
1182
1183 #endif // LIBGIG_SERIALIZATION_H

  ViewVC Help
Powered by ViewVC