1 /*******************************************************************************
2 
3     Contains extensions for Map based classes to dump the contents of maps to a
4     file or to read from a file into a map. Includes struct versioning support.
5 
6     This module provides you with several ways to load/dump a map from/into
7     a file:
8 
9     * Using the specialized version SerializingMap of the class Map
10     * Using the provided MapExtension mixin to extend a map yourself
11     * Using the class MapSerializer to use the load/dump functions directly
12 
13     See documentation of class MapSerializer for more details
14 
15     Copyright:
16         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
17         All rights reserved.
18 
19     License:
20         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
21         Alternatively, this file may be distributed under the terms of the Tango
22         3-Clause BSD License (see LICENSE_BSD.txt for details).
23 
24 *******************************************************************************/
25 
26 module ocean.util.container.map.utils.MapSerializer;
27 
28 
29 import ocean.meta.types.Qualifiers;
30 
31 import ocean.io.digest.Fnv1,
32        ocean.io.serialize.SimpleStreamSerializer,
33        ocean.io.serialize.TypeId,
34        ocean.util.container.map.Map;
35 import ocean.core.Array : copy;
36 import ocean.core.Exception;
37 
38 import ocean.core.ExceptionDefinitions    : IOException;
39 import ocean.io.model.IConduit : IOStream;
40 
41 import ocean.core.Tuple,
42        ocean.io.stream.Buffered,
43        ocean.io.device.File;
44 
45 import ocean.meta.traits.Basic;
46 
47 import ocean.util.serialize.contiguous.MultiVersionDecorator,
48        ocean.util.serialize.contiguous.Serializer,
49        ocean.util.serialize.Version;
50 
51 import ocean.text.convert.Formatter;
52 
53 /*******************************************************************************
54 
55     Temporary solution to expose protected `convert` method of version
56     decorator to this module. Eventually MapSerializer should be rewritten
57     to either inherit MultiVersionDecorator or mixin necessary methods directly.
58 
59     This is not done in first round of changes because it is potentially
60     intrusive implementation change.
61 
62 *******************************************************************************/
63 
64 private class Converter : VersionDecorator
65 {
66 }
67 
68 /*******************************************************************************
69 
70     Specialized version of class Map that includes serialization capabilities
71     using the MapExtension mixin
72 
73     Everything else is identical to the class Map, that means you still need to
74     implement your own hashing functionality.
75 
76     Note:
77         The serializer allows simple transition from using a map with a
78         non-versioned key / value to a versioned one. The version of the
79         key / value must be v0 and the layout must be the same,
80         otherwise loading will fail.
81 
82     Params:
83         V = type of the value
84         K = type of the key
85 
86 *******************************************************************************/
87 
88 abstract class SerializingMap ( V, K ) : Map!(V, K)
89 {
90     /***************************************************************************
91 
92         Mixin extensions for serialization
93 
94     ***************************************************************************/
95 
96     mixin MapExtension!(K, V);
97 
98     /***************************************************************************
99 
100         Constructor.
101 
102         Same as the Constructor of Map, but additionally initializes the
103         serializer.
104 
105     ***************************************************************************/
106 
107     protected this ( size_t n, float load_factor = 0.75 )
108     {
109         this.serializer = new MapSerializer;
110         super(n, load_factor);
111     }
112 
113     /***************************************************************************
114 
115         Constructor.
116 
117         Same as the Constructor of Map, but additionally initializes the
118         serializer.
119 
120     ***************************************************************************/
121 
122     protected this ( IAllocator allocator, size_t n, float load_factor = 0.75 )
123     {
124         this.serializer = new MapSerializer;
125         super(allocator, n, load_factor);
126     }
127 }
128 
129 /*******************************************************************************
130 
131     Template meant to be used with mixin in classes that inherit from the class
132     Map.
133 
134     Extends the class with a load() and dump() function. The mixed in class has
135     to initialize the member 'serializer' in its constructor.
136 
137     See SerializingMap for an usage example
138 
139     Params:
140         K = key type of the map
141         V = value type of the map
142 
143 *******************************************************************************/
144 
145 template MapExtension ( K, V )
146 {
147     /***************************************************************************
148 
149         Delegate used to check whether a given record should be dumped or loaded
150 
151     ***************************************************************************/
152 
153     alias bool delegate ( ref K, ref V ) CheckDg;
154 
155     /***************************************************************************
156 
157         Instance of the serializer, needs to be initialized in the class
158         constructor
159 
160     ***************************************************************************/
161 
162     protected MapSerializer serializer;
163 
164     /***************************************************************************
165 
166         Loads a file into the map
167 
168         Params:
169             file_path = path to the file
170 
171     ***************************************************************************/
172 
173     public void load ( cstring file_path )
174     {
175         this.serializer.load!(K, V)(this, file_path);
176     }
177 
178     /***************************************************************************
179 
180         Loads a file into the map
181 
182         Params:
183             io_device = the device to load from
184 
185     ***************************************************************************/
186 
187     public void load ( IConduit io_device )
188     {
189         this.serializer.loadConduit!(K, V)(this, io_device);
190     }
191 
192     /***************************************************************************
193 
194         Loads a file into the map
195 
196         Params:
197             file_path = path to the file
198             check     = function called for every entry, should return true if
199                         it should be loaded
200 
201     ***************************************************************************/
202 
203     public void load ( cstring file_path, scope CheckDg check )
204     {
205         scope file = new File(file_path, File.ReadExisting);
206 
207         this.load(file, check);
208     }
209 
210     /***************************************************************************
211 
212         Loads a file into the map
213 
214         Params:
215             io_device = the device to load from
216             check     = function called for every entry, should return true if
217                         it should be loaded
218 
219     ***************************************************************************/
220 
221     public void load ( IConduit io_device, scope CheckDg check )
222     {
223         scope add = ( ref K k, ref V v )
224         {
225             if (check(k,v))
226             {
227                 bool added = false;
228 
229                 static if ( isArrayType!(V) == ArrayKind.Dynamic )
230                 {
231                     (*this.put(k, added)).copy(v);
232                 }
233                 else
234                 {
235                     (*this.put(k, added)) = v;
236                 }
237 
238                 // If added key is an array and new don't reuse the memory it
239                 // references
240                 static if ( isArrayType!(K) == ArrayKind.Dynamic )
241                 {
242                     if ( added )
243                     {
244                         k = k.dup;
245                     }
246                 }
247             }
248         };
249 
250         this.serializer.loadDgConduit!(K, V)(io_device, add);
251     }
252 
253     /***************************************************************************
254 
255         Dumps a map into a file
256 
257         Params:
258             file_path = path to the file
259 
260     ***************************************************************************/
261 
262     public void dump ( cstring file_path )
263     {
264         this.serializer.dump!(K, V)(this, file_path);
265     }
266 
267     /***************************************************************************
268 
269         Dumps a map to an IO device
270 
271         Params:
272             io_device = the device to dump to
273 
274     ***************************************************************************/
275 
276     public void dump ( IConduit io_device )
277     {
278         this.serializer.dumpConduit!(K, V)(this, io_device);
279     }
280 
281     /***************************************************************************
282 
283         Writes a map to a file.
284 
285         Params:
286             file_path = path to where the map should be dumped to
287             check     = function called for each key/value to confirm that it
288                         should be dumped
289 
290      ***************************************************************************/
291 
292     public void dump ( cstring file_path, scope CheckDg check )
293     {
294         scope file = new File(file_path, File.Style(File.Access.Write,
295                                                     File.Open.Create,
296                                                     File.Share.None));
297 
298         this.dump(file, check);
299     }
300 
301     /***************************************************************************
302 
303         Writes a map to an IO device.
304 
305         Params:
306             io_device = the device to dump to
307             check     = function called for each key/value to confirm that it
308                         should be dumped
309 
310      ***************************************************************************/
311 
312     public void dump ( IConduit io_device, scope CheckDg check )
313     {
314         scope adder = ( void delegate ( ref K, ref V ) add )
315         {
316             foreach ( ref k, ref v; this ) if ( check(k,v) )
317             {
318                 add(k, v);
319             }
320         };
321 
322         this.serializer.dumpDgConduit!(K, V)(io_device, adder);
323     }
324 }
325 
326 /*******************************************************************************
327 
328     Offers functionality to load/dump the content of Maps (optionally of
329     anything actually, using the delegate version of the dump/load functions).
330 
331     Features include backwards compability with auto-conversion to the requested
332     struct version. It makes use of the same functions that the
333     StructLoader/StructDumper use for the conversion.
334 
335     This means that structs used with this function should have the const
336     member StructVersion as well as an alias to the old version (if one exists)
337     called "StructPrevious" (this is identical to the requirements for vesioned
338     struct in the StructDumper/Loader).
339 
340     Additionally, a validation using the struct hash is done too, to exclude
341     potential human errors while setting up the version info.
342 
343     If you have a map saved in the old version (2) and at the same time updated
344     the struct definition of that map, you can still take advantage of the
345     auto-conversion functionality if you simply define the old struct version as
346     version 0 and your current one as version 1. The loader is smart enough to
347     figure out the old version by hash and converts it to the newer one.
348 
349     Usage Example:
350     ---
351     struct MyValue0
352     {
353         const StructVersion = 0;
354         int my_value;
355     }
356 
357     auto serializer = new MapSerializer;
358     auto myMap = new HashMap!(MyValue0)(10);
359 
360     // Assume code to fill map with values here
361     //...
362     //
363 
364     serializer.dump(myMap, "version0.map");
365 
366     // Later...
367 
368     serilizer.load(myMap, "version0.map");
369 
370 
371     // Now, if you have changed the struct, create a new version of it
372 
373     struct MyValue1
374     {
375         const StructVersion = 1;
376         int my_value;
377 
378         int my_new_value;
379 
380         void convert_my_new_value ( ref MyValue0 old )
381         {
382             this.my_new_value = old.my_value * 2;
383         }
384     }
385 
386     // This is our map with the new version
387     auto myNewMap = new HashMap!(MyValue1)(10);
388 
389     // Load old version
390     serializer.load(myNewMap, "version0.map");
391 
392     // .. use map as desired.
393 
394     // You can do the same thing with the key in case it is a struct.
395     ---
396 *******************************************************************************/
397 
398 class MapSerializer
399 {
400     import ocean.io.digest.Fnv1: StaticFnv1a64;
401 
402     /***************************************************************************
403 
404         Magic Marker for HashMap files, part of the header
405 
406      ***************************************************************************/
407 
408     private static immutable uint MAGIC_MARKER = 0xCA1101AF;
409 
410     /***************************************************************************
411 
412         Current file header version
413 
414     ***************************************************************************/
415 
416     private static immutable ubyte HEADER_VERSION = 5;
417 
418     /***************************************************************************
419 
420         Exception thrown when the file that was loaded is incomplete. Will soon
421         be unused
422 
423     ***************************************************************************/
424 
425     class UnexpectedEndException : Exception
426     {
427         mixin DefaultExceptionCtor;
428     }
429 
430     /***************************************************************************
431 
432         Helper template for version handling.
433         Takes a tuple of types and changes the type and position index to what
434         ever it has as .StructPrevious member.
435 
436         Only works with tuples of length 2
437 
438         Params:
439             index = index of the type that will be made into StructPrevious
440             T...  = tuple of the types
441 
442     ***************************************************************************/
443 
444     template AddStructPrevious ( ubyte index, T... )
445     {
446         static assert ( T.length == 2 );
447         static assert ( index <= 1 );
448 
449         static if ( index == 0 )
450         {
451             alias Tuple!(T[0].StructPrevious, T[1]) AddStructPrevious;
452         }
453         else
454         {
455             alias Tuple!(T[0], T[1].StructPrevious) AddStructPrevious;
456         }
457     }
458 
459     /***************************************************************************
460 
461         Takes a type tuple and transforms it into the same type tuple but with
462         the types being pointers to the original types.
463 
464         Params:
465             T = tuple to convert
466 
467     ***************************************************************************/
468 
469     template AddPtr ( T... )
470     {
471         static if ( T.length > 0 )
472         {
473             alias Tuple!(T[0]*, AddPtr!(T[1..$])) AddPtr;
474         }
475         else
476         {
477             alias T AddPtr;
478         }
479     }
480 
481     /***************************************************************************
482 
483         Struct to be used for creating unique hash
484 
485     ***************************************************************************/
486 
487     private struct KeyValueStruct( K, V)
488     {
489         K k;
490         V v;
491     }
492 
493     /***************************************************************************
494 
495         Evaluates to the fnv1 hash of the types that make up the struct.
496         If S is no struct, mangled name of the type is used.
497 
498         Params:
499             S = struct containing key & value
500 
501     ***************************************************************************/
502 
503     template StructHash ( S )
504     {
505         static if ( is (typeof(TypeHash!(S))) )
506         {
507             static immutable StructHash = TypeHash!(S);
508         }
509         else
510         {
511             static immutable StructHash = StaticFnv1a64!(typeof(S.k).mangleof ~
512                                              typeof(S.v).mangleof);
513         }
514     }
515 
516     /***************************************************************************
517 
518         File header writen at the beginning of a dumped HashMap
519 
520     ***************************************************************************/
521 
522     private struct FileHeader ( K, V, ubyte VERSION )
523     {
524         /***********************************************************************
525 
526             Magic Marker, making sure that this file is really what we expect it
527             to be
528 
529         ***********************************************************************/
530 
531         uint marker         = MAGIC_MARKER;
532 
533         /***********************************************************************
534 
535             Version of the FileHeader. Should be changed for any modification
536 
537         ***********************************************************************/
538 
539         ubyte versionNumber = VERSION;
540 
541         /***********************************************************************
542 
543             Hash or Version of the struct types, making sure that the key and
544             value types are the same as when this file was saved.
545 
546         ***********************************************************************/
547 
548         static if ( VERSION <= 2 &&
549                     !is ( K == class) && !is (V == class) &&
550                     !is ( K == interface) && !is (V == interface) )
551         {
552             ulong hash = TypeHash!(KeyValueStruct!(K,V));
553         }
554 
555         static if ( VERSION >= 3 && VERSION <= 4 )
556         {
557             static if ( Version.Info!(K).exists )
558             {
559                 ubyte key_version = Version.Info!(K).number;
560             }
561 
562             static if ( Version.Info!(V).exists )
563             {
564                 ubyte value_version = Version.Info!(V).number;
565             }
566 
567             static if ( !Version.Info!(K).exists &&
568                         !Version.Info!(V).exists &&
569                         !is ( K == class) && !is (V == class) &&
570                         !is ( K == interface) && !is (V == interface) )
571             {
572                 ulong hash = TypeHash!(KeyValueStruct!(K,V));
573             }
574         }
575         else static if ( VERSION >= 5 )
576         {
577             ubyte key_version = Version.Info!(K).number;
578             ubyte value_version = Version.Info!(V).number;
579             ulong hash = StructHash!(KeyValueStruct!(K,V));
580         }
581     }
582 
583     /***************************************************************************
584 
585         Delegate used to put values in a map
586 
587     ***************************************************************************/
588 
589     template PutterDg ( K, V )
590     {
591         alias void delegate ( ref K, ref V ) PutterDg;
592     }
593 
594     /***************************************************************************
595 
596         Delegate used to add new values from a map
597 
598     ***************************************************************************/
599 
600     template AdderDg ( K, V )
601     {
602         alias void delegate ( void delegate ( ref K, ref V ) ) AdderDg;
603     }
604 
605     /***************************************************************************
606 
607         Pair of buffers used for conversion
608 
609     ***************************************************************************/
610 
611     struct BufferPair
612     {
613         void[] first, second;
614     }
615 
616     /***************************************************************************
617 
618         buffered output instance
619 
620     ***************************************************************************/
621 
622     private BufferedOutput buffered_output;
623 
624     /***************************************************************************
625 
626         buffered input instance
627 
628     ***************************************************************************/
629 
630     private BufferedInput buffered_input;
631 
632     /***************************************************************************
633 
634         Temporary buffers to convert value structs
635 
636     ***************************************************************************/
637 
638     private BufferPair value_convert_buffer;
639 
640     /***************************************************************************
641 
642         Temporary buffers to convert key structs
643 
644     ***************************************************************************/
645 
646     private BufferPair key_convert_buffer;
647 
648     /***************************************************************************
649 
650         Writing buffer for the StructDumper
651 
652     ***************************************************************************/
653 
654     private void[] dump_buffer;
655 
656     /***************************************************************************
657 
658         Struct converter with internal buffer
659 
660     ***************************************************************************/
661 
662     private Converter converter;
663 
664     /***************************************************************************
665 
666         Constructor
667 
668         Params:
669             buffer_size = optional, size of the input/output buffers used for
670                           reading/writing
671 
672     ***************************************************************************/
673 
674     this ( size_t buffer_size = 64 * 1024 )
675     {
676         this.buffered_output = new BufferedOutput(null, buffer_size);
677         this.buffered_input  = new BufferedInput(null, buffer_size);
678 
679         this.converter       = new Converter;
680     }
681 
682 
683     /***************************************************************************
684 
685         Writes a map to a file.
686 
687         Params:
688             map        = instance of the array map to dump
689             file_path  = path to where the map should be dumped to
690 
691     ***************************************************************************/
692 
693     public void dump ( K, V ) ( Map!(V, K) map, cstring file_path )
694     {
695         scope file = new File(file_path, File.Style(File.Access.Write,
696                                                     File.Open.Create,
697                                                     File.Share.None));
698 
699         this.dumpConduit(map, file);
700     }
701 
702 
703     /***************************************************************************
704 
705         Writes a map to an IO device.
706 
707         Params:
708             io_device = the device to dump to
709             file_path = path to where the map should be dumped to
710 
711     ***************************************************************************/
712 
713     public void dumpConduit ( K, V ) ( Map!(V, K) map, IConduit io_device )
714     {
715         scope adder = ( void delegate ( ref K, ref V ) add )
716         {
717             foreach ( ref k, ref v; map )
718             {
719                 add(k, v);
720             }
721         };
722 
723         this.dumpDgConduit!(K, V)(io_device, adder);
724     }
725 
726 
727     /***************************************************************************
728 
729         Writes a map to a file.
730 
731         Params:
732             file_path = path to where the map should be dumped to
733             adder     = function called with a delegate that can be used to add
734                         elements that are to be dumped. Once that delegate
735                         returns, the rest will be written.
736 
737     ***************************************************************************/
738 
739     public void dumpDg ( K, V ) ( cstring file_path, AdderDg!(K, V) adder )
740     {
741         scope file = new File(file_path, File.Style(File.Access.Write,
742                                                     File.Open.Create,
743                                                     File.Share.None));
744         this.dumpDgConduit!(K, V)(file, adder);
745     }
746 
747 
748     /***************************************************************************
749 
750         Writes a map to an IO device.
751 
752         Params:
753             io_device = the device to dump to
754             adder     = function called with a delegate that can be used to add
755                         elements that are to be dumped. Once that delegate
756                         returns, the rest will be written.
757 
758     ***************************************************************************/
759 
760     public void dumpDgConduit ( K, V ) ( IConduit io_device,
761         AdderDg!(K, V) adder )
762     {
763         this.buffered_output.output(io_device);
764         this.buffered_output.clear();
765 
766         this.dumpInternal!(K,V)(this.buffered_output, adder);
767     }
768 
769 
770     /***************************************************************************
771 
772         Internal dump function
773 
774         Params:
775             K = Key type of the map
776             V = Value type of the map
777             HeaderVersion = version of the file header we're trying to load
778             buffered = stream to write to
779             adder    = function called with a delegate that can be used to add
780                        elements that aare to be dumped. Once the delegate
781                        returns the writing process will be finalized
782 
783     ***************************************************************************/
784 
785     private void dumpInternal ( K, V, ubyte HeaderVersion = HEADER_VERSION )
786                               ( BufferedOutput buffered, AdderDg!(K, V) adder )
787     {
788         size_t nr_rec = 0;
789 
790         FileHeader!(K,V, HeaderVersion) fh;
791 
792         SimpleStreamSerializer.write(buffered, fh);
793         // Write dummy value first
794         SimpleStreamSerializer.write(buffered, nr_rec);
795 
796         scope addKeyVal = ( ref K key, ref V val )
797         {
798             SimpleStreamSerializerArrays.write!(K)(buffered, key);
799             SimpleStreamSerializerArrays.write!(V)(buffered, val);
800             nr_rec++;
801         };
802 
803         scope(exit)
804         {
805             buffered.flush();
806 
807             // Write actual length now
808             buffered.seek(fh.sizeof);
809             SimpleStreamSerializer.write(buffered, nr_rec);
810 
811             buffered.flush();
812         }
813 
814         adder(addKeyVal);
815     }
816 
817 
818     /***************************************************************************
819 
820         loads dumped map content from the file system
821 
822         Does not support structs with dynamic arrays yet.
823 
824         Throws:
825             Exception when the file has not the expected fileheader and
826             other Exceptions for various kinds of errors (file not found, etc)
827 
828         Params:
829             K = key of the array map
830             V = value of the corresponding key
831             map       = instance of the array map
832             file_path = path to the file to load from
833 
834     ***************************************************************************/
835 
836     public void load ( K, V ) ( Map!(V, K) map, cstring file_path )
837     {
838         scope file = new File(file_path, File.ReadExisting);
839 
840         this.loadConduit(map, file);
841     }
842 
843 
844     /***************************************************************************
845 
846         loads dumped map content from the file system
847 
848         Does not support structs with dynamic arrays yet.
849 
850         Throws:
851             Exception when the file has not the expected fileheader and
852             other Exceptions for various kinds of errors (file not found, etc)
853 
854         Params:
855             K = key of the array map
856             V = value of the corresponding key
857             map       = instance of the array map
858             io_device = the device to load from
859 
860     ***************************************************************************/
861 
862     public void loadConduit ( K, V ) ( Map!(V, K) map, IConduit io_device )
863     {
864         scope putter = ( ref K k, ref V v )
865         {
866             bool added = false;
867 
868             static if ( isArrayType!(V) == ArrayKind.Dynamic )
869             {
870                 copy(*map.put(k, added), v);
871             }
872             else
873             {
874                 (*map.put(k, added)) = v;
875             }
876 
877             // If added key is an array and new don't reuse the memory it
878             // references
879             static if ( isArrayType!(K) == ArrayKind.Dynamic )
880             {
881                 if ( added )
882                 {
883                     k = k.dup;
884                 }
885             }
886         };
887 
888         this.loadDgConduit!(K, V)(io_device, putter);
889     }
890 
891 
892     /***************************************************************************
893 
894         Loads dumped map content from the file system
895 
896         Does not support structs with dynamic arrays yet.
897 
898         Throws:
899             Exception when the file has not the expected fileheader and
900             other Exceptions for various kinds of errors (file not found, etc)
901 
902         Params:
903             K = key of the array map
904             V = value of the corresponding key
905             file_path = path to the file to load from
906             putter    = function called for each entry to insert it into the map
907 
908     ***************************************************************************/
909 
910     public void loadDg ( K, V ) ( cstring file_path, PutterDg!(K, V) putter )
911     {
912         scope file = new File(file_path, File.ReadExisting);
913 
914         this.loadDgConduit!(K, V)(file, putter);
915     }
916 
917 
918     /***************************************************************************
919 
920         Loads dumped map content from the file system
921 
922         Does not support structs with dynamic arrays yet.
923 
924         Throws:
925             Exception when the file has not the expected fileheader and
926             other Exceptions for various kinds of errors (file not found, etc)
927 
928         Params:
929             K = key of the array map
930             V = value of the corresponding key
931             io_device = the device to load from
932             putter    = function called for each entry to insert it into the map
933 
934     ***************************************************************************/
935 
936     public void loadDgConduit ( K, V ) ( IConduit io_device,
937         PutterDg!(K, V) putter )
938     {
939         this.buffered_input.input(io_device);
940 
941         loadInternal!(K,V)(this.buffered_input, putter);
942     }
943 
944 
945     /***************************************************************************
946 
947         Loads dumped map content from a input stream
948 
949         Does not support structs with dynamic arrays yet.
950 
951         Throws:
952             Exception when the file has not the expected fileheader and
953             other Exceptions for various kinds of errors (file not found, etc)
954 
955         Params:
956             K = key of the array map
957             V = value of the corresponding key
958             HeaderVersion = version of the file header we're trying to load
959             buffered  = input stream to read from
960             putter    = function called for each entry to insert it into the map
961 
962     ***************************************************************************/
963 
964     private void loadInternal ( K, V, ubyte HeaderVersion = HEADER_VERSION )
965                               ( BufferedInput buffered, PutterDg!(K, V) putter )
966     {
967         bool raw_load = false;
968 
969         FileHeader!(K,V, HeaderVersion) fh_expected;
970         FileHeader!(K,V, HeaderVersion) fh_actual;
971 
972         fh_actual.versionNumber = ubyte.max;
973 
974         buffered.seek(0);
975         buffered.compress();
976         buffered.populate();
977 
978         SimpleStreamSerializer.read(buffered, fh_actual);
979 
980         if ( fh_actual.marker != fh_expected.marker )
981         {
982             throw new Exception(format("Expected Magic Marker {}, got {}",
983                                        fh_expected.marker, fh_actual.marker),
984                                 __FILE__, __LINE__);
985         }
986         else if ( fh_actual.versionNumber != fh_expected.versionNumber )
987         {
988             if ( fh_actual.versionNumber == 2 )
989             {
990                 return this.loadOld!(K,V)(buffered, putter);
991             }
992             if ( fh_actual.versionNumber == 3 )
993             {
994                 raw_load = true;
995             }
996             else if ( fh_actual.versionNumber == 4 )
997             {
998                 return this.loadInternal!(K, V, 4)(buffered, putter);
999             }
1000             else
1001             {
1002                 throw new Exception(
1003                     format("Expected version of file header to be {}, but got"
1004                            ~ " {}, aborting!", fh_expected.versionNumber,
1005                            fh_actual.versionNumber),
1006                     __FILE__, __LINE__);
1007             }
1008         }
1009 
1010         bool conv;
1011 
1012         // Code for converting from older Key/Value structs
1013         static if ( is ( typeof ( fh_expected.key_version ) ) )
1014         {
1015             if ( fh_expected.key_version != 0 )
1016             {
1017                 conv = this.handleVersion!(MapSerializer.loadInternal, 0, K, V)
1018                             (fh_actual.key_version, fh_expected.key_version,
1019                              this.key_convert_buffer, putter, buffered);
1020 
1021                 if ( conv ) return;
1022             }
1023         }
1024 
1025         static if ( is ( typeof ( fh_expected.value_version ) ) )
1026         {
1027             if ( fh_expected.value_version != 0 )
1028             {
1029                 conv = this.handleVersion!(MapSerializer.loadInternal, 1, K, V)
1030                             (fh_actual.value_version, fh_expected.value_version,
1031                              this.value_convert_buffer, putter, buffered);
1032 
1033                 if ( conv ) return;
1034             }
1035         }
1036 
1037         static if ( is ( typeof ( fh_expected.hash ) ) )
1038         {
1039             if ( fh_expected.hash != fh_actual.hash )
1040             {
1041                 throw new Exception(
1042                     format("File struct differ from struct used to load "
1043                            ~ "(expected 0x{:X} but got 0x{:X})!",
1044                            fh_expected.hash, fh_actual.hash),
1045                     __FILE__, __LINE__);
1046             }
1047         }
1048 
1049         size_t nr_rec;
1050 
1051         if ( buffered.readable < nr_rec.sizeof )
1052         {
1053             buffered.compress();
1054             buffered.populate();
1055         }
1056         SimpleStreamSerializerArrays.read(buffered, nr_rec);
1057 
1058         for ( ulong i=0; i < nr_rec;i++ )
1059         {
1060             K key;
1061             V value;
1062 
1063             if ( buffered.readable < V.sizeof + K.sizeof )
1064             {
1065                 buffered.compress();
1066                 buffered.populate();
1067             }
1068 
1069             if ( raw_load )
1070             {
1071                 SimpleStreamSerializer.read!(K)(buffered, key);
1072                 SimpleStreamSerializer.read!(V)(buffered, value);
1073             }
1074             else
1075             {
1076                 SimpleStreamSerializerArrays.read!(K)(buffered, key);
1077                 SimpleStreamSerializerArrays.read!(V)(buffered, value);
1078             }
1079 
1080             putter(key, value);
1081         }
1082     }
1083 
1084 
1085     /***************************************************************************
1086 
1087         Checks if a struct needs to be converted and converts it if required
1088 
1089         Params:
1090             loadFunc = function to use to load older version of the struct
1091             index    = index of the type in the tuple that should be
1092                        checked/converted
1093             T        = tuple of key/value types
1094             actual   = version that was found in the data
1095             expected = version that is desired
1096             buffer   = conversion buffer to use
1097             putter   = delegate to use to put the data into the map
1098             buffered = buffered input stream
1099 
1100         Returns:
1101             true if conversion happened, else false
1102 
1103         Throws:
1104             if conversion failed
1105 
1106     ***************************************************************************/
1107 
1108     private bool handleVersion ( alias loadFunc, size_t index, T... )
1109                                ( Version.Type actual,
1110                                  Version.Type expected,
1111                                  ref BufferPair buffer,
1112                                  scope void delegate ( ref T ) putter,
1113                                  BufferedInput buffered )
1114     {
1115         if ( actual < expected )
1116         {
1117             return this.tryConvert!(true, MapSerializer.loadInternal, index, T)
1118                                    (buffer, putter, buffered);
1119         }
1120 
1121         return false;
1122     }
1123 
1124 
1125     /***************************************************************************
1126 
1127         Checks if a struct needs to be converted and converts it if required
1128 
1129         Params:
1130             throw_if_unable = if true, an exception is thrown if we can't
1131                               convert this struct
1132             loadFunc = function to use to load older version of the struct
1133             index    = index of the type in the tuple that should be
1134                        checked/converted
1135             T        = tuple of key/value types
1136             buffer   = conversion buffer to use
1137             putter   = delegate to use to put the data into the map
1138             buffered = buffered input stream
1139 
1140         Returns:
1141             true if a conversion happened, false if we can't convert it and
1142             throw_if_unable is false
1143 
1144         Throws:
1145             if throw_if_unable is true and we couldn't convert it
1146 
1147     ***************************************************************************/
1148 
1149     private bool tryConvert ( bool throw_if_unable, alias loadFunc,
1150                               size_t index, T... )
1151                             ( ref BufferPair buffer,
1152                               scope void delegate ( ref T ) putter,
1153                               BufferedInput buffered )
1154     {
1155         static assert ( T.length == 2 );
1156 
1157         static immutable other = index == 1 ? 0 : 1;
1158 
1159         static if ( Version.Info!(T[index]).exists )
1160         {
1161             // Normally we should be able to use `MissingVersion.exists`
1162             // directly (which is `false`), but it triggers a DMD bug in D1.
1163             // DMD doesn't seem able to do anything with
1164             // `Version.Info!(T[index]).prev` except compare it with a type.
1165             // This can be checked by replacing `can_convert` with:
1166             // `const can_convert = Version.Info!(T[index]).prev.exists;`
1167             static immutable can_convert
1168                 = !is(Version.Info!(T[index]).prev == MissingVersion);
1169         }
1170         else
1171         {
1172             static immutable can_convert = false;
1173         }
1174 
1175         static if ( can_convert )
1176         {
1177             alias AddStructPrevious!(index, T) TWithPrev;
1178 
1179             scope convPut = ( ref TWithPrev keyval )
1180             {
1181                 auto buf = &keyval[index] is buffer.first.ptr ?
1182                                 &buffer.second : &buffer.first;
1183 
1184                 AddPtr!(T) res;
1185 
1186                 Serializer.serialize(keyval[index], *buf);
1187                 res[index] = this.converter.convert!(T[index], TWithPrev[index])(*buf).ptr;
1188                 res[other] = &keyval[other];
1189 
1190                 putter(*res[0], *res[1]);
1191             };
1192 
1193             loadFunc!(TWithPrev)(buffered, convPut);
1194 
1195             return true;
1196         }
1197         else static if ( throw_if_unable )
1198         {
1199             throw new Exception("Cannot convert to new version!",
1200                                 __FILE__, __LINE__);
1201         }
1202         else
1203         {
1204             return false;
1205         }
1206     }
1207 
1208 
1209     /***************************************************************************
1210 
1211         Previous load function, kept so that old versions can still be loaded
1212 
1213         Params:
1214             K = type of the key
1215             V = type of the value
1216             buffered = stream to read from
1217             putter   = delegate used to write loaded records to the map
1218 
1219     ***************************************************************************/
1220 
1221     private void loadOld ( K, V ) ( BufferedInput buffered,
1222                                     scope void delegate ( ref K, ref V ) putter )
1223     {
1224         size_t nr_rec;
1225 
1226         FileHeader!(K,V,2) fh_expected;
1227         FileHeader!(K,V,2) fh_actual;
1228 
1229         buffered.seek(0);
1230         buffered.compress();
1231         buffered.populate();
1232 
1233         SimpleStreamSerializer.read(buffered, fh_actual);
1234 
1235         if ( fh_actual.marker != fh_expected.marker )
1236         {
1237             throw new Exception("Magic Marker mismatch");
1238         }
1239         else if ( fh_actual.versionNumber != fh_expected.versionNumber )
1240         {
1241             throw new Exception("Version of file header "
1242                               ~ " does not match our version, aborting!");
1243         }
1244         else static if ( is ( typeof ( fh_expected.hash ) ) )
1245         if ( fh_actual.hash != fh_expected.hash )
1246         {
1247             bool conv;
1248 
1249             // convert from a previous key struct to the current
1250             conv = this.tryConvert!(false, MapSerializer.loadOld, 0, K, V)
1251                 (this.key_convert_buffer, putter, buffered);
1252 
1253             if ( conv ) return;
1254 
1255             // convert from a previous value struct to the current
1256             conv = this.tryConvert!(false, MapSerializer.loadOld, 1, K, V)
1257                                    (this.value_convert_buffer, putter, buffered);
1258 
1259             if ( conv ) return;
1260 
1261             throw new Exception("Unable to convert structs " ~ K.stringof ~ ", " ~
1262                                 V.stringof ~
1263                                 " to our structs, aborting!");
1264         }
1265 
1266         if ( buffered.readable < nr_rec.sizeof )
1267         {
1268             buffered.compress();
1269             buffered.populate();
1270         }
1271         SimpleStreamSerializerArrays.read(buffered, nr_rec);
1272 
1273         for ( ulong i=0; i < nr_rec;i++ )
1274         {
1275             K key;
1276             V value;
1277 
1278             if ( buffered.readable < V.sizeof + K.sizeof )
1279             {
1280                 buffered.compress();
1281                 buffered.populate();
1282             }
1283 
1284             SimpleStreamSerializer.read!(K)(buffered, key);
1285             SimpleStreamSerializer.read!(V)(buffered, value);
1286             putter(key, value);
1287         }
1288     }
1289 }
1290 
1291 
1292 /*******************************************************************************
1293 
1294     Unittests
1295 
1296 *******************************************************************************/
1297 
1298 version (unittest)
1299 {
1300     import ocean.io.device.MemoryDevice,
1301            ocean.io.digest.Fnv1,
1302            ocean.core.Test,
1303            ocean.util.container.map.model.StandardHash,
1304            ocean.util.container.map.Map,
1305            ocean.util.container.map.HashMap;
1306 
1307     import ocean.meta.types.Arrays;
1308 
1309     /***************************************************************************
1310 
1311         Slightly specialized version of Map that would simply take the raw value
1312         of structs to hash them
1313 
1314     ***************************************************************************/
1315 
1316     class StructhashMap (V, K) : Map!(V, K)
1317     {
1318         /***********************************************************************
1319 
1320             Constructor
1321 
1322             Params:
1323                 n = amount of expected elements
1324 
1325         ***********************************************************************/
1326 
1327         public this ( size_t n )
1328         {
1329             super(n);
1330         }
1331 
1332         /***********************************************************************
1333 
1334             Dumb and easy toHash method.
1335 
1336             Simply takes the key and passes it directly to fnv1a.
1337 
1338             Parameters:
1339                 key = key of which the hash is desired
1340 
1341             Returns:
1342                 hash of the key
1343 
1344         ***********************************************************************/
1345 
1346         public override hash_t toHash ( in K key )
1347         {
1348             return Fnv1a.fnv1(key);
1349         }
1350 
1351     }
1352 
1353     /***************************************************************************
1354 
1355         Dump function that dumps in the old format, to test whether we can still
1356         read it (and convert it)
1357 
1358         Params:
1359             K = type of key
1360             V = type of value
1361             buffered = output stream to write to
1362             adder    = delegate called with a delegate that can be used to add
1363                        values
1364 
1365     ***************************************************************************/
1366 
1367     void dumpOld ( K, V ) ( BufferedOutput buffered,
1368                             MapSerializer.AdderDg!(K, V) adder )
1369     {
1370         size_t nr_rec = 0;
1371 
1372         MapSerializer.FileHeader!(K,V,2) fh;
1373 
1374         SimpleStreamSerializer.write(buffered, fh);
1375         // Write dummy value for now
1376         SimpleStreamSerializer.write(buffered, nr_rec);
1377 
1378         scope addKeyVal = ( ref K key, ref V val )
1379         {
1380             SimpleStreamSerializer.write!(K)(buffered, key);
1381             SimpleStreamSerializer.write!(V)(buffered, val);
1382             nr_rec++;
1383         };
1384 
1385         scope(exit)
1386         {
1387             buffered.flush();
1388 
1389             // Write actual length now
1390             buffered.seek(fh.sizeof);
1391             SimpleStreamSerializer.write(buffered, nr_rec);
1392 
1393             buffered.flush();
1394         }
1395 
1396         adder(addKeyVal);
1397     }
1398 
1399     /***************************************************************************
1400 
1401         Test writing & loading of the given combination of struct types on a
1402         virtual file
1403 
1404         Depending on the exact combinations, the key structs should offer the
1405         following methods:
1406 
1407         * compare ( <other_key_struct> * ) - compare the two structs
1408         * K old() - convert this struct to the older one
1409 
1410         The value structs should offer only a compare function.
1411 
1412         Params:
1413             K = type of the key to write
1414             V = type of the value to write
1415             KNew = type of the key to read, automatic conversion will happen
1416             VNew = type of the value to read, automatic conversion will happen
1417             custom_dump = optional, custom code to use for dumping the data.
1418                           The code is expected to define a variable
1419                           "header_size" containing the size of the header
1420 
1421     ***************************************************************************/
1422 
1423     void testCombination ( K, V, KNew, VNew, istring custom_dump = "" )
1424               ( size_t iterations )
1425     {
1426         auto t = new NamedTest("Combination {" ~
1427             "K = " ~ K.stringof ~
1428             ", V = " ~ V.stringof ~
1429             ", KNew = " ~ KNew.stringof ~
1430             ", VNew = " ~ VNew.stringof ~ "}"
1431         );
1432         static immutable ValueArraySize = 200;
1433 
1434         scope array = new MemoryDevice;
1435         scope map   = new StructhashMap!(V, K)(iterations);
1436         scope serializer = new MapSerializer;
1437 
1438         // helper to get a key from size_t
1439         K initKey ( int i )
1440         {
1441             static if ( is ( K == struct ) )
1442             {
1443                 return K(i);
1444             }
1445             else
1446             {
1447                 return i;
1448             }
1449         }
1450 
1451         // helper to get old key from new key
1452         K fromNew ( KNew k )
1453         {
1454             static if ( is ( K == struct ) )
1455             {
1456                 return k.old();
1457             }
1458             else
1459             {
1460                 return k;
1461             }
1462         }
1463 
1464         V initVal ( int i )
1465         {
1466             static if ( isArrayType!(V) == ArrayKind.Dynamic )
1467             {
1468                 alias ElementTypeOf!(V) VA;
1469 
1470                 auto r = new VA[ValueArraySize];
1471 
1472                 foreach ( ref e; r )
1473                 {
1474                     e = VA(i);
1475                 }
1476 
1477                 return r;
1478             }
1479             else return V(i);
1480         }
1481 
1482         // Fill test map
1483         for ( int i = 0; i < iterations; ++i )
1484         {
1485             *map.put(initKey(i)) = initVal(i);
1486         }
1487 
1488         scope adder = ( void delegate ( ref K, ref V ) add )
1489         {
1490             foreach ( ref k, ref v; map )
1491             {
1492                 add(k, v);
1493             }
1494         };
1495 
1496         // Dump test map (to memory)
1497         static if ( custom_dump.length > 0 )
1498         {
1499             mixin(custom_dump);
1500         }
1501         else
1502         {
1503             serializer.buffered_output.output(array);
1504             serializer.dumpInternal!(K, V)(serializer.buffered_output, adder);
1505 
1506             auto header_size = MapSerializer.FileHeader!(K, V, MapSerializer.HEADER_VERSION).sizeof;
1507         }
1508 
1509         // Check size of dump
1510         static if ( isArrayType!(V) == ArrayKind.Dynamic )
1511         {
1512             t.test(array.bufferSize() ==
1513                         (K.sizeof + size_t.sizeof +
1514                             ElementTypeOf!(V).sizeof * ValueArraySize) *
1515                                 iterations + header_size + size_t.sizeof,
1516                     "Written size is not the expected value!");
1517         }
1518         else
1519         {
1520             t.test(array.bufferSize() == (K.sizeof + V.sizeof) *
1521                     iterations + header_size + size_t.sizeof,
1522                     "Written size is not the expected value!");
1523         }
1524 
1525         // Check load function
1526         size_t amount_loaded = 0;
1527         scope checker = ( ref KNew k, ref VNew v )
1528         {
1529             amount_loaded++;
1530             static if ( isArrayType!(VNew) == ArrayKind.Dynamic )
1531             {
1532                 foreach ( i, el; v )
1533                 {
1534                     t.test(el.compare(&(*map.get(fromNew(k)))[i]),
1535                         "Loaded item unequal saved item!");
1536                 }
1537             }
1538             else
1539             {
1540                 t.test(v.compare(map.get(fromNew(k))),
1541                     "Loaded item unequal saved item!");
1542             }
1543         };
1544 
1545         array.seek(0);
1546         serializer.buffered_input.input(array);
1547         serializer.loadInternal!(KNew, VNew)(serializer.buffered_input, checker);
1548 
1549         t.test(amount_loaded == map.length,
1550                "Amount of loaded items unequal amount of written items!");
1551     }
1552 }
1553 
1554 unittest
1555 {
1556     static immutable Iterations = 10_000;
1557 
1558     static immutable old_load_code =
1559           `scope bufout = new BufferedOutput(array, 2048);
1560            bufout.seek(0);
1561            dumpOld!(K, V)(bufout, adder);
1562 
1563            auto header_size = MapSerializer.FileHeader!(K, V, 2).sizeof;`;
1564 
1565     static immutable version4_load_code =
1566           `serializer.buffered_output.output(array);
1567            serializer.dumpInternal!(K, V, 4)(serializer.buffered_output, adder);
1568 
1569            auto header_size = MapSerializer.FileHeader!(K, V, 4).sizeof;
1570            `;
1571 
1572     static struct TestNoVersion
1573     {
1574         long i;
1575 
1576         static TestNoVersion opCall ( long i )
1577         {
1578             TestNoVersion t;
1579             t.i = i*2;
1580             return t;
1581         }
1582 
1583         bool compare ( TestNoVersion* other )
1584         {
1585             return i == other.i;
1586         }
1587     }
1588 
1589     static struct Test1
1590     {
1591         enum StructVersion = 0;
1592 
1593         long i;
1594     }
1595 
1596     static struct Test2
1597     {
1598         enum StructVersion = 1;
1599         alias Test1 StructPrevious;
1600 
1601         long i;
1602         long o;
1603         static void convert_o ( ref Test1 t, ref Test2 dst ) { dst.o = t.i+1; }
1604 
1605         bool compare ( Test1* old )
1606         {
1607             return old.i == i && old.i+1 == o;
1608         }
1609 
1610         bool compare ( Test2* old )
1611         {
1612             return *old == this;
1613         }
1614     }
1615 
1616     static struct OldStruct
1617     {
1618         enum StructVersion = 0;
1619 
1620         int old;
1621 
1622         bool compare ( OldStruct * o )
1623         {
1624             return *o == this;
1625         }
1626     }
1627 
1628     static struct NewStruct
1629     {
1630         enum StructVersion = 1;
1631         alias OldStruct StructPrevious;
1632 
1633         int old;
1634 
1635         int a_bit_newer;
1636         static void convert_a_bit_newer ( ref OldStruct, ref NewStruct dst )
1637         {
1638             dst.a_bit_newer = dst.old+1;
1639         }
1640 
1641         bool compare ( OldStruct* old )
1642         {
1643             return old.old == this.old &&
1644                    old.old+1 == a_bit_newer;
1645         }
1646     }
1647 
1648     static struct OldKey
1649     {
1650         enum StructVersion = 0;
1651 
1652         int old2;
1653 
1654         bool compare ( OldKey * o )
1655         {
1656             return *o == this;
1657         }
1658 
1659         OldKey old ( )
1660         {
1661             return this;
1662         }
1663     }
1664 
1665     static struct NewKey
1666     {
1667         enum StructVersion = 1;
1668         alias OldKey StructPrevious;
1669 
1670         int old1;
1671 
1672         static void convert_old1 ( ref OldKey o, ref NewKey dst )
1673         {
1674             dst.old1 = o.old2;
1675         }
1676 
1677         int newer;
1678 
1679         static void convert_newer ( ref OldKey o, ref NewKey dst )
1680         {
1681             dst.newer = o.old2+1;
1682         }
1683 
1684         bool compare ( OldKey * oldk )
1685         {
1686             return oldk.old2 == old1 && oldk.old2+1 == newer;
1687         }
1688 
1689         OldKey old ( )
1690         {
1691             return OldKey(old1);
1692         }
1693     }
1694 
1695     static struct NewerKey
1696     {
1697         enum StructVersion = 2;
1698         alias NewKey StructPrevious;
1699 
1700         int old1;
1701         int wops;
1702 
1703         static void convert_wops ( ref NewKey k, ref NewerKey dst )
1704         {
1705             dst.wops = k.old1;
1706         }
1707 
1708         bool compare ( NewKey * n )
1709         {
1710             return n.old1 == old1 && wops == n.old1;
1711         }
1712 
1713         OldKey old ( )
1714         {
1715             return OldKey(old1);
1716         }
1717     }
1718 
1719     static struct NewerStruct
1720     {
1721         enum StructVersion = 2;
1722         alias NewStruct StructPrevious;
1723 
1724         int old;
1725         long of;
1726 
1727         static void convert_of ( ref NewStruct n, ref NewerStruct dst )
1728         {
1729             dst.of = n.a_bit_newer;
1730         }
1731 
1732         bool compare ( OldStruct * olds )
1733         {
1734             return olds.old == old;
1735         }
1736     }
1737 
1738     static struct NoPrevious
1739     {
1740         enum StructVersion = 42;
1741 
1742         int hello;
1743 
1744         bool compare ( NoPrevious* olds )
1745         {
1746             return olds.hello == hello;
1747         }
1748     }
1749 
1750     static struct SinglePrevious
1751     {
1752         enum StructVersion = 42;
1753         int hello42;
1754 
1755         static void convert_hello42 ( ref StructPrevious p, ref SinglePrevious dst )
1756         {
1757             dst.hello42 = p.hello + 42;
1758         }
1759 
1760         bool compare ( StructPrevious* olds )
1761         {
1762             return this.hello42 == (olds.hello + 42);
1763         }
1764 
1765         static struct StructPrevious
1766         {
1767             enum StructVersion = 41;
1768             alias SinglePrevious StructNext;
1769             int hello;
1770 
1771             bool compare ( StructNext* news )
1772             {
1773                 return this.hello == (news.hello42 - 42);
1774             }
1775         }
1776     }
1777 
1778 
1779     // Test creation of a SerializingMap instance
1780     class HashingSerializingMap : SerializingMap!(int,int)
1781     {
1782         public this ( size_t n, float load_factor = 0.75 )
1783         {
1784             super(n, load_factor);
1785         }
1786 
1787         override:
1788             mixin StandardHash.toHash!(int);
1789     }
1790 
1791 
1792     // Test same and old version
1793     testCombination!(hash_t, Test1, hash_t, Test2)(Iterations);
1794     testCombination!(hash_t, Test2, hash_t, Test2)(Iterations);
1795 
1796     // Test Arrays
1797     testCombination!(hash_t, Test2[], hash_t, Test2[])(Iterations);
1798 
1799     // Test unversioned structs
1800     testCombination!(hash_t, TestNoVersion, hash_t, TestNoVersion)(Iterations);
1801 
1802     // Test old versions
1803     testCombination!(hash_t, TestNoVersion, hash_t, TestNoVersion, old_load_code)(Iterations);
1804 
1805     // Test conversion of old files to new ones
1806     testCombination!(hash_t, OldStruct, hash_t, NewStruct, old_load_code)(Iterations);
1807 
1808     // Test conversion of old files with
1809     // different key versions to new ones
1810     testCombination!(OldKey, OldStruct, OldKey, OldStruct, old_load_code)(Iterations);
1811     testCombination!(OldKey, OldStruct, NewKey, OldStruct, old_load_code)(Iterations);
1812     testCombination!(OldKey, OldStruct, NewKey, OldStruct, old_load_code)(Iterations);
1813     testCombination!(OldKey, OldStruct, NewKey, NewStruct, old_load_code)(Iterations);
1814     testCombination!(OldKey, OldStruct, NewerKey, NewStruct, old_load_code)(Iterations);
1815     testCombination!(OldKey, OldStruct, NewerKey, NewerStruct,old_load_code)(Iterations);
1816 
1817 
1818     ////////////// Test Loading of version 4 header
1819 
1820     // Test old versions
1821     testCombination!(hash_t, TestNoVersion, hash_t, TestNoVersion, version4_load_code)(Iterations);
1822 
1823     // Test conversion of old files to new ones
1824     testCombination!(hash_t, OldStruct, hash_t, NewStruct, version4_load_code)(Iterations);
1825 
1826     // Test conversion of old files with
1827     // different key versions to new ones
1828     testCombination!(OldKey, OldStruct, OldKey, OldStruct, version4_load_code)(Iterations);
1829     testCombination!(OldKey, OldStruct, NewKey, OldStruct, version4_load_code)(Iterations);
1830     testCombination!(OldKey, OldStruct, NewKey, OldStruct, version4_load_code)(Iterations);
1831     testCombination!(OldKey, OldStruct, NewKey, NewStruct, version4_load_code)(Iterations);
1832     testCombination!(OldKey, OldStruct, NewerKey, NewStruct, version4_load_code)(Iterations);
1833     testCombination!(OldKey, OldStruct, NewerKey, NewerStruct,version4_load_code)(Iterations);
1834 
1835     // Test that structs that stripped backward compatibily can be used
1836     testCombination!(hash_t, NoPrevious, hash_t, NoPrevious)(Iterations);
1837     testCombination!(hash_t, SinglePrevious.StructPrevious,
1838                      hash_t, SinglePrevious)(Iterations);
1839 }
1840 
1841 version (unittest)
1842 {
1843     // Make sure structs with a StructNext can be instantiated
1844     struct S ( ubyte V )
1845     {
1846         enum StructVersion = V;
1847 
1848         // comment this out and it works
1849         static if (V == 0)
1850             alias S!(1) StructNext;
1851 
1852         static if (V == 1)
1853             alias S!(0) StructPrevious;
1854     }
1855 
1856     unittest
1857     {
1858         alias SerializingMap!(S!(0), ulong) Map;
1859     }
1860 }
1861 
1862 unittest
1863 {
1864     class Map : SerializingMap!(int, int)
1865     {
1866         this ( ) { super(10); }
1867 
1868         // This method has to accept `const(int)` because it overrides templated
1869         // base `toHash (K) (in K key)` method and DMD demands exact qualifier
1870         // match for arguments.
1871         public override hash_t toHash ( in int key ) { return key; }
1872     }
1873 
1874     scope device = new MemoryDevice;
1875     auto map = new Map;
1876     *map.put(10) = 20;
1877     map.dump(device);
1878 
1879     map.clear();
1880     test!("==")(map.length, 0);
1881     device.seek(0);
1882 
1883     map.load(device);
1884 
1885     test!("==")(map.length, 1);
1886     test!("in")(10, map);
1887     test!("==")(map[10], 20);
1888 }