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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3773 - (show annotations) (download) (as text)
Tue May 19 14:28:20 2020 UTC (3 years, 11 months ago) by schoenebeck
File MIME type: text/x-c++hdr
File size: 53584 byte(s)
* Serialization.h: Fixed assertion fault on some systems if a member
  variable was serialised which was declared as size_t or ssize_t
  data type.

* Bumped version (4.2.0.svn11).

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

  ViewVC Help
Powered by ViewVC