1 /******************************************************************************
2 
3     Struct data serialization and deserialization tools
4 
5     Used for plugin-based serialization and stream serialization. For binary
6     struct serialization use `ocean.util.serialize.contiguous` package.
7 
8     Copyright:
9         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
10         All rights reserved.
11 
12     License:
13         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
14         Alternatively, this file may be distributed under the terms of the Tango
15         3-Clause BSD License (see LICENSE_BSD.txt for details).
16 
17 *******************************************************************************/
18 
19 module ocean.io.serialize.StructSerializer;
20 
21 
22 import ocean.meta.types.Qualifiers;
23 
24 import ocean.io.serialize.SimpleStreamSerializer;
25 
26 import ocean.core.Exception;
27 
28 import ocean.io.model.IConduit: IOStream, InputStream, OutputStream;
29 
30 import ocean.meta.traits.Basic;
31 import ocean.meta.traits.Indirections : hasIndirections;
32 import ocean.meta.types.Arrays : ElementTypeOf, StripAllArrays;
33 import ocean.meta.types.Typedef : castToBase;
34 
35 import ocean.meta.codegen.Identifier;
36 
37 
38 
39 /*******************************************************************************
40 
41     SerializerException
42 
43 *******************************************************************************/
44 
45 class SerializerException : Exception
46 {
47     mixin ReusableExceptionImplementation!();
48     alias .LengthMismatch LengthMismatch;
49 }
50 
51 /*******************************************************************************
52 
53     StructSerializer Exception
54     Reusable exception instance.
55 
56 *******************************************************************************/
57 
58 class LengthMismatch : SerializerException
59 {
60     size_t bytes_expected, bytes_got;
61 
62     this ( size_t bytes_expected, size_t bytes_got,
63            istring msg, istring file, typeof(__LINE__) line )
64     {
65         this.set(msg, file, line);
66         this.bytes_expected = bytes_expected;
67         this.bytes_got      = bytes_got;
68     }
69  }
70 
71 /*******************************************************************************
72 
73     Reusable exception instance.
74 
75 *******************************************************************************/
76 
77 private SerializerException serializer_exception;
78 
79 static this ()
80 {
81     .serializer_exception = new SerializerException();
82 }
83 
84 /*******************************************************************************
85 
86     Struct serializer
87 
88     Params:
89         AllowUnions = if true, unions will be serialized as raw bytes, without
90             checking whether the union contains dynamic arrays. Otherwise unions
91             cause a compile-time error.
92 
93     TODO: proper union support -- must recurse into unions looking for dynamic
94     arrays
95 
96 *******************************************************************************/
97 
98 struct StructSerializer ( bool AllowUnions = false )
99 {
100     static:
101 
102     /***************************************************************************
103 
104         Dumps/serializes the content of s and its array members, writing
105         serialized data to output.
106 
107         Params:
108             s      = struct instance (pointer)
109             output = output stream to write serialized data to
110 
111         Returns:
112             number of bytes written
113 
114     ***************************************************************************/
115 
116     size_t dump ( S ) ( S* s, OutputStream output )
117     {
118         return dump(s, (void[] data) {SimpleStreamSerializer.transmit(output, data);});
119     }
120 
121     /***************************************************************************
122 
123         Dumps/serializes the content of s and its array members.
124 
125         send is called repeatedly; on each call, it must store or forward the
126         provided data.
127 
128         Params:
129             s    = struct instance (pointer)
130             receive = receiving callback delegate
131 
132         Returns:
133             number of bytes written
134 
135     ***************************************************************************/
136 
137     size_t dump ( S ) ( S* s, scope void delegate ( void[] data ) receive )
138     {
139         enforceStructPtr!("dump")(s);
140         return transmit!(false)(s, receive);
141     }
142 
143     /***************************************************************************
144 
145         Loads/deserializes the content of s and its array members, reading
146         serialized data from input.
147 
148         Params:
149             s     = struct instance (pointer)
150             input = input stream to read data from
151 
152         Returns:
153             number of bytes read
154 
155     ***************************************************************************/
156 
157     size_t load ( S ) ( S* s, InputStream input )
158     {
159         return load(s, (void[] data) {SimpleStreamSerializer.transmit(input, data);});
160     }
161 
162     /***************************************************************************
163 
164         Loads/deserializes the content of s and its array members.
165 
166         receive is called repeatedly; on each call, it must populate the
167         provided data buffer with data previously produced by dump(). Data which
168         was populated once should not be populated again. So the delegate must
169         behave like a stream receive function.
170 
171         Params:
172             s       = struct instance (pointer)
173             receive = receiving callback delegate
174 
175         Returns:
176             number of bytes read
177 
178     ***************************************************************************/
179 
180     size_t load ( S ) ( S* s, scope void delegate ( void[] data ) receive )
181     {
182         enforceStructPtr!("load")(s);
183         return transmit!(true)(s, receive);
184     }
185 
186     /***************************************************************************
187 
188         Dumps/serializes or loads/deserializes the content of s and its
189         members.
190 
191         transmit_data is called repeatedly; on each call,
192          - if receive is false, it must it must store or forward the provided
193            data;
194          - if receive is true, it must populate the provided data buffer with
195            data previously produced by dump(). Data which was populated once
196            should not be populated again. So the delegate must behave like a
197            stream receive function.
198 
199         Params:
200             s             = struct instance (pointer)
201             transmit_data = sending/receiving callback delegate
202 
203         Returns:
204             number of bytes read or written
205 
206     ***************************************************************************/
207 
208     size_t transmit ( bool receive, S ) ( S* s, scope void delegate ( void[] data ) transmit_data )
209     {
210         .serializer_exception.enforce(s !is null,
211                 typeof (this).stringof ~ ".transmit (receive = " ~
212                 receive.stringof ~ "): source pointer of type '" ~ S.stringof ~
213                 "*' is null");
214 
215         S s_copy = *s;
216 
217         S* s_copy_ptr = &s_copy;
218 
219         static if (receive)
220         {
221             transmit_data((cast (void*) s)[0 .. S.sizeof]);
222 
223             copyReferences(s_copy_ptr, s);
224         }
225         else
226         {
227             resetReferences(s_copy_ptr);
228 
229             transmit_data((cast (void*) s_copy_ptr)[0 .. S.sizeof]);
230         }
231 
232         return S.sizeof + transmitArrays!(receive)(s, transmit_data);
233     }
234 
235 
236     /***************************************************************************
237 
238         Dumps/serializes the content of s and its array members, using the given
239         serializer object. The serializer object needs the following methods:
240 
241             void open ( D, cstring name );
242 
243             void close ( D, cstring name );
244 
245             void serialize ( T ) ( D, ref T item, cstring name );
246 
247             void openStruct ( D, cstring name );
248 
249             void closeStruct ( D, cstring name );
250 
251             void serializeArray ( T ) ( D, cstring name, T[] array );
252 
253               Optional:
254 
255                 void serializeStaticArray ( T ) ( D, cstring name, T[] array );
256 
257               If this methond doesn't exist, serializeArray will be used.
258 
259             void openStructArray ( T ) ( D, cstring name, T[] array );
260 
261             void closeStructArray ( T ) ( D, cstring name, T[] array );
262 
263         Unfortunately, as some of these methods are templates, it's not
264         possible to make an interface for it. But the compiler will let you know
265         whether a given serializer object is suitable or not
266 
267         See ocean.io.serialize.JsonStructSerializer for an example.
268 
269         Params:
270             S = type of struct to serialize
271             Serializer = type of serializer object
272             D = tuple of data parameters passed to the serializer
273             s    = struct instance (pointer)
274             serializer = object to do the serialization
275             data = parameters for serializer
276 
277     ***************************************************************************/
278 
279     public void serialize ( S, Serializer, D ... ) ( S* s, Serializer serializer, ref D data )
280     {
281         serializer.open(data, S.stringof);
282         serialize_(s, serializer, data);
283         serializer.close(data, S.stringof);
284     }
285 
286 
287     /***************************************************************************
288 
289         Loads/deserializes the content of s and its array members, using the
290         given deserializer object. The deserializer object needs the following
291         methods:
292 
293                 void open ( ref Char[] input, cstring name );
294 
295                 void close ( );
296 
297                 void deserialize ( T ) ( ref T output, cstring name );
298 
299                 void deserializeStruct ( ref T output, Char[] name, void delegate ( ) deserialize_struct );
300 
301                 void deserializeArray ( T ) ( ref T[] output, Char[] name );
302 
303                 void deserializeStaticArray ( T ) ( T[] output, Char[] name );
304 
305                 void deserializeStructArray ( T ) ( ref T[] output, Char[] name, void delegate ( ref T ) deserialize_element );
306 
307         Unfortunately, as some of these methods are templates, it's not
308         possible to make an interface for it. But the compiler will let you know
309         whether a given deserializer object is suitable or not
310 
311         See ocean.io.serialize.JsonStructDeserializer for an example.
312 
313         Params:
314             s = struct instance (pointer)
315             deserializer = object to do the deserialization
316             data = input buffer to read serialized data from
317 
318     ***************************************************************************/
319 
320     public void deserialize ( S, Deserializer, D ) ( S* s, Deserializer deserializer, D[] data )
321     {
322         deserializer.open(data, S.stringof);
323         deserialize_(s, deserializer, data);
324         deserializer.close();
325     }
326 
327     /**************************************************************************
328 
329         Resets all references in s to null.
330 
331         Params:
332             s = struct instance (pointer)
333 
334     ***************************************************************************/
335 
336     S* resetReferences ( S ) ( S* s )
337     {
338         foreach (i, ref field; s.tupleof)
339         {
340             alias typeof(field) T;
341 
342             static if (is (T == struct))
343             {
344                 resetReferences(&field);                                         // recursive call
345             }
346             else static if (isReferenceType!(T))
347             {
348                 field = null;
349             }
350         }
351 
352         return s;
353     }
354 
355     /***************************************************************************
356 
357         Copies all references from dst to src.
358 
359         Params:
360             src = source struct instance (pointer)
361             dst = destination struct instance (pointer)
362 
363     ***************************************************************************/
364 
365     S* copyReferences ( S ) ( S* src, S* dst )
366     {
367         foreach (i, ref src_field; src.tupleof)
368         {
369             alias typeof(src_field) T;
370 
371             T* dst_field = &dst.tupleof[i];
372 
373             static if (is (T == struct))
374             {
375                 copyReferences(&src_field, dst_field);                           // recursive call
376             }
377             else static if (isReferenceType!(T))
378             {
379                 *dst_field = src_field;
380             }
381         }
382 
383         return dst;
384     }
385 
386     /***************************************************************************
387 
388         Transmits (sends or receives) the serialized data of all array fields in
389         s.
390 
391         Template parameter:
392             receive = true: receive array data, false: send array data
393 
394         Params:
395             s        = struct instance (pointer)
396             transmit = sending/receiving callback delegate
397 
398         Returns:
399             passes through return value of transmit
400 
401         FIXME: Does currently not scan static array fields for a struct type
402         containing dynamic arrays. Example:
403 
404          ---
405              struct S1
406              {
407                  int[] x;
408              }
409 
410              struct S2
411              {
412 
413                  S1[7] y;   // elements contain a dynamic array
414              }
415          ---
416 
417     ***************************************************************************/
418 
419     size_t transmitArrays ( bool receive, S ) ( S* s, scope void delegate ( void[] array ) transmit )
420     {
421         size_t bytes = 0;
422 
423         foreach (i, ref field; s.tupleof)
424         {
425             alias typeof(field) T;
426 
427             static if (is (T == struct))
428             {
429                 bytes += transmitArrays!(receive)(&field, transmit);             // recursive call
430             }
431             else static if (is (T U == U[]))
432             {
433                 mixin AssertSupportedArray!(T, U, S, i);
434 
435                 bytes += transmitArray!(receive)(field, transmit);
436             }
437             else mixin AssertSupportedType!(T, S, i);
438         }
439 
440         return bytes;
441     }
442 
443     /***************************************************************************
444 
445         Transmits (sends or receives) the serialized data of array. That is,
446         first transmit the array content byte length as size_t value, then the
447         array content raw data.
448 
449         Template parameter:
450             receive = true: receive array data, false: send array data
451 
452         Params:
453             array    = array to send serialized data of (pointer)
454             transmit_dg = sending/receiving callback delegate
455 
456         Returns:
457             passes through return value of send
458 
459         TODO: array needs to be duped
460 
461     ***************************************************************************/
462 
463     size_t transmitArray ( bool receive, T ) ( ref T[] array, scope void delegate ( void[] data ) transmit_dg )
464     {
465         size_t len,
466                bytes = len.sizeof;
467 
468         static if (!receive)
469         {
470             len = array.length;
471         }
472 
473         transmit_dg((cast (void*) &len)[0 .. len.sizeof]);
474 
475         static if (receive)
476         {
477             array.length = len;
478         }
479 
480         static if (is (T == struct))                                            // recurse into substruct
481         {                                                                       // if it contains dynamic
482             enum RecurseIntoStruct = hasIndirections!(typeof (T.tupleof));// arrays
483         }
484         else
485         {
486             enum RecurseIntoStruct = false;
487         }
488 
489         static if (is (T U == U[]))                                             // recurse into subarray
490         {
491             foreach (ref element; array)
492             {
493                 bytes += transmitArray!(receive)(element, transmit_dg);
494             }
495         }
496         else static if (RecurseIntoStruct)
497         {
498             debug ( StructSerializer )
499                 pragma (msg, typeof (this).stringof  ~ ".transmitArray: "
500                              ~ "array elements of struct type '" ~ T.stringof ~
501                              ~ "' contain subarrays");
502 
503             foreach (ref element; array)
504             {
505                 bytes += transmit!(receive)(&element, transmit_dg);
506             }
507         }
508         else
509         {
510             size_t n = len * T.sizeof;
511 
512             transmit_dg((cast (void*) array.ptr)[0 .. n]);
513 
514             bytes += n;
515         }
516 
517         return bytes;
518     }
519 
520     /**************************************************************************
521 
522         Dumps/serializes the content of s and its array members, using the given
523         serializer object. See the description of the dump() method above for a
524         full description of how the serializer object should behave.
525 
526         Params:
527             S = type of struct to serialize
528             Serializer = type of serializer object
529             D = tuple of data parameters passed to the serializer
530             s = struct instance (pointer)
531             serializer = object to do the serialization
532             data = parameters for serializer
533 
534     ***************************************************************************/
535 
536     private void serialize_ ( S, Serializer, D ... ) ( S* s, Serializer serializer, ref D data )
537     {
538         foreach (i, ref field; s.tupleof)
539         {
540             alias typeof(field) T;
541             enum field_name = identifier!(S.tupleof[i]);
542 
543             // imitate D1 style formatting for D2 typedef struct
544             static if ( is(T == struct) && !isTypedef!(T) )
545             {
546                 serializer.openStruct(data, field_name);
547                 serialize_(&field, serializer, data);                            // recursive call
548                 serializer.closeStruct(data, field_name);
549             }
550             else static if ( is(T U : U[]) )
551             {
552                 // slice array (passing a static array as ref is not allowed)
553                 U[] array = field;
554                 alias StripAllArrays!(U) Element;
555 
556                 // imitate D1 style formatting for arrays of D2 typedef structs
557                 static if ( is(Element == struct) && !isTypedef!(Element) )
558                 {
559                     serializeStructArray(array, field_name, serializer, data);
560                 }
561                 else static if (
562                        isArrayType!(T) == ArrayKind.Static
563                     && is(typeof(serializer.serializeStaticArray!(T))) )
564                 {
565                     serializer.serializeStaticArray(data, field_name, array);
566                 }
567                 else
568                 {
569                     serializer.serializeArray(data, field_name, array);
570                 }
571             }
572             else
573             {
574                 mixin AssertSupportedType!(T, S, i);
575 
576                 static if ( isTypedef!(T) == TypedefKind.Keyword )
577                 {
578                     auto lvalue = castToBase(field);
579                     serializer.serialize(data, lvalue, field_name);
580                 }
581                 else static if ( is(T B == enum) )
582                 {
583                     auto lvalue = cast(B)(field);
584                     serializer.serialize(data, lvalue, field_name);
585                 }
586                 else
587                 {
588                     serializer.serialize(data, field, field_name);
589                 }
590             }
591         }
592     }
593 
594     /***************************************************************************
595 
596         Dumps/serializes array which is expected to be a one- or multi-
597         dimensional array of structs, using the given serializer object. See the
598         description of the dump() method above for a full description of how the
599         serializer object should behave.
600 
601         Params:
602             T = array base type, should be a struct or a (possibly
603                 multi-dimensional) array of structs
604             Serializer = type of serializer object
605             D = tuple of data parameters passed to the serializer
606             array = array to serialize
607             field_name = the name of the struct field that contains the array
608             serializer = object to do the serialization
609             data = parameters for serializer
610 
611     ***************************************************************************/
612 
613     private void serializeStructArray ( T, Serializer, D ... ) ( T[] array,
614         cstring field_name, Serializer serializer, ref D data )
615     {
616         serializer.openStructArray(data, field_name, array);
617 
618         foreach ( ref element; array )
619         {
620             static if ( is(T U : U[]) )
621             {
622                 serializeStructArray(element, field_name, serializer, data);
623             }
624             else
625             {
626                 static assert(is(T == struct));
627                 serializer.openStruct(data, T.stringof);
628                 serialize_(&element, serializer, data);
629                 serializer.closeStruct(data, T.stringof);
630             }
631         }
632 
633         serializer.closeStructArray(data, field_name, array);
634     }
635 
636     /***************************************************************************
637 
638         Loads/deserializes the content of s and its array members, using the
639         given deserializer object. See the description of the load() method
640         above for a full description of how the deserializer object should
641         behave.
642 
643         Params:
644             s = struct instance (pointer)
645             deserializer = object to do the deserialization
646             data = input buffer to read serialized data from
647 
648     ***************************************************************************/
649 
650     private void deserialize_ ( S, Deserializer, D ) ( S* s, Deserializer deserializer, D[] data )
651     {
652         foreach (i, T; typeof (S.tupleof))
653         {
654             T*   field      = &s.tupleof[i];
655             auto field_name = identifier!(S.tupleof[i]);
656 
657             static if ( is(T == struct) )
658             {
659                 deserializer.openStruct(data, field_name);
660                 deserialize_(field, serializer, data);                          // recursive call
661                 deserializer.closeStruct(data, field_name);
662             }
663             else static if ( is(T U : U[]) )
664             {
665                 static if ( is(U == struct) )
666                 {
667                     deserializer.openStructArray(data, field_name, array);
668                     foreach ( element; array )
669                     {
670                         deserializer.openStruct(data, U.stringof);
671                         deserialize_(&element, serializer, data);               // recursive call
672                         deserializer.closeStruct(data, U.stringof);
673                     }
674                     deserializer.closeStructArray(data, field_name, array);
675                 }
676                 else
677                 {
678                     static if ( isStaticArrayType!(T) )
679                     {
680                         deserializer.deserializeStaticArray(*field, field_name);
681                     }
682                     else
683                     {
684                         deserializer.deserializeArray(*field, field_name);
685                     }
686                 }
687             }
688             else
689             {
690                 mixin AssertSupportedType!(T, S, i);
691 
692                 static if ( isTypedef!(T) == TypedefKind.Keyword )
693                 {
694                     mixin(`
695                     else static if ( is(T B == typedef) )
696                     {
697                         deserializer.deserialize(cast(B)(*field), field_name);
698                     }
699                     `);
700                 }
701                 else static if ( is(T B == enum) )
702                 {
703                     deserializer.deserialize(cast(B)(*field), field_name);
704                 }
705                 else
706                 {
707                     deserializer.deserialize(*field, field_name);
708                 }
709             }
710         }
711     }
712 
713     /***************************************************************************
714 
715         Asserts s != null; s is assumed to be the struct source or destination
716         pointer. In addition a warning message is printed at compile time if
717         S is a pointer to a pointer.
718         The s != null checking is done in assert() fashion; that is, it is not
719         done in release mode.
720 
721         Params:
722             func = invoking function (for message generation)
723             s = pointer to a source or destination struct; shall not be null
724 
725         Throws:
726             Exception if s is null
727 
728     ***************************************************************************/
729 
730     private void enforceStructPtr ( istring func, S ) ( S* s )
731     {
732         static if (is (S T == T*))
733         {
734             pragma (msg, typeof (this).stringof ~ '.' ~ func ~ " - warning: "
735                   ~ "passing struct pointer argument of type '" ~ (S*).stringof ~
736                   ~ "' (you probably want '" ~ (T*).stringof ~ "')");
737         }
738 
739         .serializer_exception.enforce(s, typeof (this).stringof ~ '.' ~ func ~ ": "
740                ~ "pointer of type '" ~ S.stringof ~ "*' is null");
741     }
742 
743     /***************************************************************************
744 
745         Asserts that T, which is the type of the i-th field of S, is a supported
746         field type for struct serialization; typedefs and unions are currently
747         not supported.
748         Warns if T is an associative array.
749 
750         Template parameters:
751             T = type to check
752             S = struct type (for message generation)
753             i = struct field index (for message generation)
754 
755     ***************************************************************************/
756 
757     template AssertSupportedType ( T, S, size_t i )
758     {
759         static assert (AllowUnions || !is (T == union),
760                        typeof (this).stringof ~ ": unions are not supported, sorry "
761                       ~ "(affects " ~ FieldInfo!(T, S, i) ~ ") -- use AllowUnions "
762                       ~ "template flag to enable shallow serialization of unions");
763 
764         static if (isArrayType!(T) == ArrayKind.Associative)
765             pragma (msg, typeof (this).stringof ~
766                     ~ " - Warning: content of associative array will be discarded "
767                     ~ "(affects " ~ FieldInfo!(T, S, i) ~ ')');
768     }
769 
770     /***************************************************************************
771 
772         Asserts that T, which is an array of U and the type of the i-th field of
773         S, is a supported array field type for struct serialization;
774         multi-dimensional arrays and arrays of reference types or structs are
775         currently not supported.
776 
777         Template parameter:
778             T = type to check
779             U = element type of array type T
780             S = struct type (for message generation)
781             i = struct field index (for message generation)
782 
783     ***************************************************************************/
784 
785     template AssertSupportedArray ( T, U, S, size_t i )
786     {
787        static if (is (U V == V[]))
788        {
789            static assert (!isReferenceType!(V), typeof (this).stringof
790                           ~ ": arrays of reference types are not supported, "
791                           ~ "sorry (affects " ~ FieldInfo!(T, S, i) ~ ')');
792        }
793        else
794        {
795            static assert (!isReferenceType!(U), typeof (this).stringof
796                           ~ ": arrays of reference types are not supported, "
797                           ~ "sorry (affects " ~ FieldInfo!(T, S, i) ~ ')');
798        }
799     }
800 
801     /***************************************************************************
802 
803         Generates a struct field information string for messages
804 
805     ***************************************************************************/
806 
807     template FieldInfo ( T, S, size_t i )
808     {
809         enum FieldInfo = '\'' ~ S.tupleof[i].stringof ~ "' of type '" ~ T.stringof ~ '\'';
810     }
811 }
812 
813 
814 /*******************************************************************************
815 
816     Test for plugin serializer
817 
818 *******************************************************************************/
819 
820 version (unittest)
821 {
822     import ocean.core.Test;
823 
824     struct TestSerializer
825     {
826         import ocean.text.convert.Formatter;
827 
828         void open ( ref char[] dst, cstring name )
829         {
830             dst ~= "{";
831         }
832 
833         void close ( ref char[] dst, cstring name )
834         {
835             dst ~= "}";
836         }
837 
838         void serialize ( T ) ( ref char[] dst, ref T item, cstring name )
839         {
840             sformat(dst, "{} {}={} ", T.stringof, name, item);
841         }
842 
843         void openStruct ( ref char[] dst, cstring name )
844         {
845             dst ~= name ~ "={";
846         }
847 
848         void closeStruct ( ref char[] dst, cstring name )
849         {
850             dst ~= "} ";
851         }
852 
853         void serializeArray ( T ) ( ref char[] dst, cstring name, T[] array )
854         {
855             static if ( is(T == char) )
856             {
857                 sformat(dst, "{}[] {}=\"{}\" ", T.stringof, name, array);
858             }
859             else
860             {
861                 sformat(dst, "{}[] {}={} ", T.stringof, name, array);
862             }
863         }
864 
865         void serializeStaticArray ( T ) ( ref char[] dst, cstring name, T[] array )
866         {
867             sformat(dst, "{}[{}] {}={} ", T.stringof, array.length, name, array);
868         }
869 
870         void openStructArray ( T ) ( ref char[] dst, cstring name, T[] array )
871         {
872             dst ~= name ~ "={";
873         }
874 
875         void closeStructArray ( T ) ( ref char[] dst, cstring name, T[] array )
876         {
877             dst ~= "} ";
878         }
879     }
880 }
881 
882 unittest
883 {
884     struct TestStruct
885     {
886         mstring name;
887         int[] numbers;
888         int x;
889         float y;
890         struct InnerStruct
891         {
892             int z;
893         }
894 
895         int[4] static_array;
896         InnerStruct a_struct;
897         InnerStruct[] some_structs;
898     }
899 
900     TestStruct s;
901     s.name = "hello".dup;
902     s.numbers = [12, 23];
903     s.some_structs.length = 2;
904 
905     TestSerializer ser;
906     char[] dst;
907     StructSerializer!().serialize(&s, ser, dst);
908     test!("==")(dst, "{char[] name=\"hello\" int[] numbers=[12, 23] int x=0 float y=nan int[4] static_array=[0, 0, 0, 0] a_struct={int z=0 } some_structs={InnerStruct={int z=0 } InnerStruct={int z=0 } } }"[]);
909 }
910 
911 
912 /*******************************************************************************
913 
914     Unittests
915 
916 *******************************************************************************/
917 
918 version (unittest)
919 {
920 
921 
922     import core.stdc.time;
923     import ocean.util.Convert : to;
924     import ocean.time.StopWatch;
925     import core.memory;
926     debug ( OceanPerformanceTest ) import ocean.io.Stdout : Stderr;
927 
928     /***************************************************************************
929 
930         Provides a growing container. It will overwrite the oldest entries as soon
931         as the maxLength is reached.
932 
933     ***************************************************************************/
934 
935     struct CircularBuffer_ (T)
936     {
937         /***********************************************************************
938 
939             growing array of elements
940 
941         ***********************************************************************/
942 
943         T[] elements;
944 
945         /***********************************************************************
946 
947            maximum allowed size of the array
948 
949         ***********************************************************************/
950 
951         size_t maxLength = 50;
952 
953         /***********************************************************************
954 
955             current write position
956 
957         ***********************************************************************/
958 
959         size_t write = 0;
960 
961         /***********************************************************************
962 
963             Pushes an element on the Cache. If maxLength isn't reached, resizes
964             the cache. If it is reached, overwrites the oldest element
965 
966             Params:
967                 element = The element to push into the cache
968 
969         ***********************************************************************/
970 
971         void push (T element)
972         {
973             if (this.elements.length == this.write)
974             {
975                 if (this.elements.length < this.maxLength)
976                 {
977                     this.elements.length = this.elements.length + 1;
978                 }
979                 else
980                 {
981                     this.write = 0;
982                 }
983             }
984 
985             static if (isArrayType!(T))
986             {
987                 this.elements[this.write].length = element.length;
988                 this.elements[this.write][] = element[];
989             }
990             else
991             {
992                 this.elements[this.write] = element;
993             }
994 
995             ++this.write;
996         }
997 
998         /***********************************************************************
999 
1000             Returns the offset-newest element. Defaults to 0 (the newest)
1001 
1002             Params:
1003                 offset = the offset-newest element. The higher this number, the
1004                          older the returned element. Defaults to zero. (the newest
1005                          element)
1006 
1007         ***********************************************************************/
1008 
1009         T* get (size_t offset=0)
1010         {
1011             if (offset < this.elements.length)
1012             {
1013                 if (cast(int)(this.write - 1 - offset) < 0)
1014                 {
1015                     return &elements[$ - offset + this.write - 1];
1016                 }
1017 
1018                 return &elements[this.write - 1 - offset];
1019             }
1020 
1021             throw new Exception("Element does not exist");
1022         }
1023     }
1024 
1025     alias CircularBuffer_!(char[]) Urls;
1026 
1027 
1028     /***************************************************************************
1029 
1030         Retargeting profile
1031 
1032     ***************************************************************************/
1033 
1034     struct RetargetingAction
1035     {
1036         hash_t id;
1037         hash_t adpan_id;
1038         time_t lastseen;
1039         ubyte action;
1040 
1041 
1042         static RetargetingAction opCall(hash_t id,hash_t adpan_id,time_t lastseen,
1043                                         ubyte action)
1044         {
1045 
1046             RetargetingAction a = { id,adpan_id,lastseen,action };
1047 
1048             return a;
1049         }
1050     }
1051 
1052     /***************************************************************************
1053 
1054         Retargeting list
1055 
1056     ***************************************************************************/
1057 
1058     alias CircularBuffer_!(RetargetingAction) Retargeting;
1059 
1060     struct MeToo(int deep)
1061     {
1062         uint a;
1063         char[] jo;
1064         int[2] staticArray;
1065         static if(deep > 0)
1066             MeToo!(deep-1) rec;
1067 
1068         static if(deep > 0)
1069             static MeToo opCall(uint aa, char[] jo, int sta, int stb,MeToo!(deep-1) rec)
1070             {
1071                 MeToo a = {aa,jo,[sta,stb],rec};
1072                 return a;
1073             }
1074         else
1075             static MeToo!(0) opCall(uint aa, char[] jo, int sta, int stb,)
1076             {
1077                 MeToo!(0) a = {aa,jo,[sta,stb]};
1078                 return a;
1079             }
1080     }
1081 }