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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3476 - (show annotations) (download) (as text)
Wed Feb 20 19:12:49 2019 UTC (5 years, 1 month ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 51910 byte(s)
* Added MSVC build support
  (anonymous patch from mailing list).
* Introduced CMake build support (yet constrained for building with MSVC)
  (anonymous patch from mailing list).
* Bumped version (4.1.0.svn12).

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

  ViewVC Help
Powered by ViewVC