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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

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

  ViewVC Help
Powered by ViewVC