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 moduleocean.util.container.map.utils.MapSerializer;
27 28 29 importocean.meta.types.Qualifiers;
30 31 importocean.io.digest.Fnv1,
32 ocean.io.serialize.SimpleStreamSerializer,
33 ocean.io.serialize.TypeId,
34 ocean.util.container.map.Map;
35 importocean.core.Array : copy;
36 importocean.core.Exception;
37 38 importocean.core.ExceptionDefinitions : IOException;
39 importocean.io.model.IConduit : IOStream;
40 41 importocean.core.Tuple,
42 ocean.io.stream.Buffered,
43 ocean.io.device.File;
44 45 importocean.meta.traits.Basic;
46 47 importocean.util.serialize.contiguous.MultiVersionDecorator,
48 ocean.util.serialize.contiguous.Serializer,
49 ocean.util.serialize.Version;
50 51 importocean.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 privateclassConverter : VersionDecorator65 {
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 abstractclassSerializingMap ( V, K ) : Map!(V, K)
89 {
90 /***************************************************************************
91 92 Mixin extensions for serialization
93 94 ***************************************************************************/95 96 mixinMapExtension!(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 protectedthis ( size_tn, floatload_factor = 0.75 )
108 {
109 this.serializer = newMapSerializer;
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 protectedthis ( IAllocatorallocator, size_tn, floatload_factor = 0.75 )
123 {
124 this.serializer = newMapSerializer;
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 templateMapExtension ( K, V )
146 {
147 /***************************************************************************
148 149 Delegate used to check whether a given record should be dumped or loaded
150 151 ***************************************************************************/152 153 aliasbooldelegate ( refK, refV ) CheckDg;
154 155 /***************************************************************************
156 157 Instance of the serializer, needs to be initialized in the class
158 constructor
159 160 ***************************************************************************/161 162 protectedMapSerializerserializer;
163 164 /***************************************************************************
165 166 Loads a file into the map
167 168 Params:
169 file_path = path to the file
170 171 ***************************************************************************/172 173 publicvoidload ( cstringfile_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 publicvoidload ( IConduitio_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 publicvoidload ( cstringfile_path, scopeCheckDgcheck )
204 {
205 scopefile = newFile(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 publicvoidload ( IConduitio_device, scopeCheckDgcheck )
222 {
223 scopeadd = ( refKk, refVv )
224 {
225 if (check(k,v))
226 {
227 booladded = false;
228 229 staticif ( isArrayType!(V) == ArrayKind.Dynamic )
230 {
231 (*this.put(k, added)).copy(v);
232 }
233 else234 {
235 (*this.put(k, added)) = v;
236 }
237 238 // If added key is an array and new don't reuse the memory it239 // references240 staticif ( 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 publicvoiddump ( cstringfile_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 publicvoiddump ( IConduitio_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 publicvoiddump ( cstringfile_path, scopeCheckDgcheck )
293 {
294 scopefile = newFile(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 publicvoiddump ( IConduitio_device, scopeCheckDgcheck )
313 {
314 scopeadder = ( voiddelegate ( refK, refV ) add )
315 {
316 foreach ( refk, refv; 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 classMapSerializer399 {
400 importocean.io.digest.Fnv1: StaticFnv1a64;
401 402 /***************************************************************************
403 404 Magic Marker for HashMap files, part of the header
405 406 ***************************************************************************/407 408 privatestaticimmutableuintMAGIC_MARKER = 0xCA1101AF;
409 410 /***************************************************************************
411 412 Current file header version
413 414 ***************************************************************************/415 416 privatestaticimmutableubyteHEADER_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 classUnexpectedEndException : Exception426 {
427 mixinDefaultExceptionCtor;
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 templateAddStructPrevious ( ubyteindex, T... )
445 {
446 staticassert ( T.length == 2 );
447 staticassert ( index <= 1 );
448 449 staticif ( index == 0 )
450 {
451 aliasTuple!(T[0].StructPrevious, T[1]) AddStructPrevious;
452 }
453 else454 {
455 aliasTuple!(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 templateAddPtr ( T... )
470 {
471 staticif ( T.length > 0 )
472 {
473 aliasTuple!(T[0]*, AddPtr!(T[1..$])) AddPtr;
474 }
475 else476 {
477 aliasTAddPtr;
478 }
479 }
480 481 /***************************************************************************
482 483 Struct to be used for creating unique hash
484 485 ***************************************************************************/486 487 privatestructKeyValueStruct( K, V)
488 {
489 Kk;
490 Vv;
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 templateStructHash ( S )
504 {
505 staticif ( is (typeof(TypeHash!(S))) )
506 {
507 staticimmutableStructHash = TypeHash!(S);
508 }
509 else510 {
511 staticimmutableStructHash = 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 privatestructFileHeader ( K, V, ubyteVERSION )
523 {
524 /***********************************************************************
525 526 Magic Marker, making sure that this file is really what we expect it
527 to be
528 529 ***********************************************************************/530 531 uintmarker = MAGIC_MARKER;
532 533 /***********************************************************************
534 535 Version of the FileHeader. Should be changed for any modification
536 537 ***********************************************************************/538 539 ubyteversionNumber = 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 staticif ( VERSION <= 2 &&
549 !is ( K == class) && !is (V == class) &&
550 !is ( K == interface) && !is (V == interface) )
551 {
552 ulonghash = TypeHash!(KeyValueStruct!(K,V));
553 }
554 555 staticif ( VERSION >= 3 && VERSION <= 4 )
556 {
557 staticif ( Version.Info!(K).exists )
558 {
559 ubytekey_version = Version.Info!(K).number;
560 }
561 562 staticif ( Version.Info!(V).exists )
563 {
564 ubytevalue_version = Version.Info!(V).number;
565 }
566 567 staticif ( !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 ulonghash = TypeHash!(KeyValueStruct!(K,V));
573 }
574 }
575 elsestaticif ( VERSION >= 5 )
576 {
577 ubytekey_version = Version.Info!(K).number;
578 ubytevalue_version = Version.Info!(V).number;
579 ulonghash = StructHash!(KeyValueStruct!(K,V));
580 }
581 }
582 583 /***************************************************************************
584 585 Delegate used to put values in a map
586 587 ***************************************************************************/588 589 templatePutterDg ( K, V )
590 {
591 aliasvoiddelegate ( refK, refV ) PutterDg;
592 }
593 594 /***************************************************************************
595 596 Delegate used to add new values from a map
597 598 ***************************************************************************/599 600 templateAdderDg ( K, V )
601 {
602 aliasvoiddelegate ( voiddelegate ( refK, refV ) ) AdderDg;
603 }
604 605 /***************************************************************************
606 607 Pair of buffers used for conversion
608 609 ***************************************************************************/610 611 structBufferPair612 {
613 void[] first, second;
614 }
615 616 /***************************************************************************
617 618 buffered output instance
619 620 ***************************************************************************/621 622 privateBufferedOutputbuffered_output;
623 624 /***************************************************************************
625 626 buffered input instance
627 628 ***************************************************************************/629 630 privateBufferedInputbuffered_input;
631 632 /***************************************************************************
633 634 Temporary buffers to convert value structs
635 636 ***************************************************************************/637 638 privateBufferPairvalue_convert_buffer;
639 640 /***************************************************************************
641 642 Temporary buffers to convert key structs
643 644 ***************************************************************************/645 646 privateBufferPairkey_convert_buffer;
647 648 /***************************************************************************
649 650 Writing buffer for the StructDumper
651 652 ***************************************************************************/653 654 privatevoid[] dump_buffer;
655 656 /***************************************************************************
657 658 Struct converter with internal buffer
659 660 ***************************************************************************/661 662 privateConverterconverter;
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_tbuffer_size = 64 * 1024 )
675 {
676 this.buffered_output = newBufferedOutput(null, buffer_size);
677 this.buffered_input = newBufferedInput(null, buffer_size);
678 679 this.converter = newConverter;
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 publicvoiddump ( K, V ) ( Map!(V, K) map, cstringfile_path )
694 {
695 scopefile = newFile(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 publicvoiddumpConduit ( K, V ) ( Map!(V, K) map, IConduitio_device )
714 {
715 scopeadder = ( voiddelegate ( refK, refV ) add )
716 {
717 foreach ( refk, refv; 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 publicvoiddumpDg ( K, V ) ( cstringfile_path, AdderDg!(K, V) adder )
740 {
741 scopefile = newFile(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 publicvoiddumpDgConduit ( K, V ) ( IConduitio_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 privatevoiddumpInternal ( K, V, ubyteHeaderVersion = HEADER_VERSION )
786 ( BufferedOutputbuffered, AdderDg!(K, V) adder )
787 {
788 size_tnr_rec = 0;
789 790 FileHeader!(K,V, HeaderVersion) fh;
791 792 SimpleStreamSerializer.write(buffered, fh);
793 // Write dummy value first794 SimpleStreamSerializer.write(buffered, nr_rec);
795 796 scopeaddKeyVal = ( refKkey, refVval )
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 now808 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 publicvoidload ( K, V ) ( Map!(V, K) map, cstringfile_path )
837 {
838 scopefile = newFile(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 publicvoidloadConduit ( K, V ) ( Map!(V, K) map, IConduitio_device )
863 {
864 scopeputter = ( refKk, refVv )
865 {
866 booladded = false;
867 868 staticif ( isArrayType!(V) == ArrayKind.Dynamic )
869 {
870 copy(*map.put(k, added), v);
871 }
872 else873 {
874 (*map.put(k, added)) = v;
875 }
876 877 // If added key is an array and new don't reuse the memory it878 // references879 staticif ( 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 publicvoidloadDg ( K, V ) ( cstringfile_path, PutterDg!(K, V) putter )
911 {
912 scopefile = newFile(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 publicvoidloadDgConduit ( K, V ) ( IConduitio_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 privatevoidloadInternal ( K, V, ubyteHeaderVersion = HEADER_VERSION )
965 ( BufferedInputbuffered, PutterDg!(K, V) putter )
966 {
967 boolraw_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 thrownewException(format("Expected Magic Marker {}, got {}",
983 fh_expected.marker, fh_actual.marker),
984 __FILE__, __LINE__);
985 }
986 elseif ( fh_actual.versionNumber != fh_expected.versionNumber )
987 {
988 if ( fh_actual.versionNumber == 2 )
989 {
990 returnthis.loadOld!(K,V)(buffered, putter);
991 }
992 if ( fh_actual.versionNumber == 3 )
993 {
994 raw_load = true;
995 }
996 elseif ( fh_actual.versionNumber == 4 )
997 {
998 returnthis.loadInternal!(K, V, 4)(buffered, putter);
999 }
1000 else1001 {
1002 thrownewException(
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 boolconv;
1011 1012 // Code for converting from older Key/Value structs1013 staticif ( 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 staticif ( 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 staticif ( is ( typeof ( fh_expected.hash ) ) )
1038 {
1039 if ( fh_expected.hash != fh_actual.hash )
1040 {
1041 thrownewException(
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_tnr_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 ( ulongi=0; i < nr_rec;i++ )
1059 {
1060 Kkey;
1061 Vvalue;
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 else1075 {
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 privateboolhandleVersion ( aliasloadFunc, size_tindex, T... )
1109 ( Version.Typeactual,
1110 Version.Typeexpected,
1111 refBufferPairbuffer,
1112 scopevoiddelegate ( refT ) putter,
1113 BufferedInputbuffered )
1114 {
1115 if ( actual < expected )
1116 {
1117 returnthis.tryConvert!(true, MapSerializer.loadInternal, index, T)
1118 (buffer, putter, buffered);
1119 }
1120 1121 returnfalse;
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 privatebooltryConvert ( boolthrow_if_unable, aliasloadFunc,
1150 size_tindex, T... )
1151 ( refBufferPairbuffer,
1152 scopevoiddelegate ( refT ) putter,
1153 BufferedInputbuffered )
1154 {
1155 staticassert ( T.length == 2 );
1156 1157 staticimmutableother = index == 1 ? 0 : 1;
1158 1159 staticif ( 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 with1164 // `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 staticimmutablecan_convert1168 = !is(Version.Info!(T[index]).prev == MissingVersion);
1169 }
1170 else1171 {
1172 staticimmutablecan_convert = false;
1173 }
1174 1175 staticif ( can_convert )
1176 {
1177 aliasAddStructPrevious!(index, T) TWithPrev;
1178 1179 scopeconvPut = ( refTWithPrevkeyval )
1180 {
1181 autobuf = &keyval[index] isbuffer.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 returntrue;
1196 }
1197 elsestaticif ( throw_if_unable )
1198 {
1199 thrownewException("Cannot convert to new version!",
1200 __FILE__, __LINE__);
1201 }
1202 else1203 {
1204 returnfalse;
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 privatevoidloadOld ( K, V ) ( BufferedInputbuffered,
1222 scopevoiddelegate ( refK, refV ) putter )
1223 {
1224 size_tnr_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 thrownewException("Magic Marker mismatch");
1238 }
1239 elseif ( fh_actual.versionNumber != fh_expected.versionNumber )
1240 {
1241 thrownewException("Version of file header "1242 ~ " does not match our version, aborting!");
1243 }
1244 elsestaticif ( is ( typeof ( fh_expected.hash ) ) )
1245 if ( fh_actual.hash != fh_expected.hash )
1246 {
1247 boolconv;
1248 1249 // convert from a previous key struct to the current1250 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 current1256 conv = this.tryConvert!(false, MapSerializer.loadOld, 1, K, V)
1257 (this.value_convert_buffer, putter, buffered);
1258 1259 if ( conv ) return;
1260 1261 thrownewException("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 ( ulongi=0; i < nr_rec;i++ )
1274 {
1275 Kkey;
1276 Vvalue;
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 importocean.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 importocean.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 classStructhashMap (V, K) : Map!(V, K)
1317 {
1318 /***********************************************************************
1319 1320 Constructor
1321 1322 Params:
1323 n = amount of expected elements
1324 1325 ***********************************************************************/1326 1327 publicthis ( size_tn )
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 publicoverridehash_ttoHash ( inKkey )
1347 {
1348 returnFnv1a.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 voiddumpOld ( K, V ) ( BufferedOutputbuffered,
1368 MapSerializer.AdderDg!(K, V) adder )
1369 {
1370 size_tnr_rec = 0;
1371 1372 MapSerializer.FileHeader!(K,V,2) fh;
1373 1374 SimpleStreamSerializer.write(buffered, fh);
1375 // Write dummy value for now1376 SimpleStreamSerializer.write(buffered, nr_rec);
1377 1378 scopeaddKeyVal = ( refKkey, refVval )
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 now1390 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 voidtestCombination ( K, V, KNew, VNew, istringcustom_dump = "" )
1424 ( size_titerations )
1425 {
1426 autot = newNamedTest("Combination {" ~
1427 "K = " ~ K.stringof ~
1428 ", V = " ~ V.stringof ~
1429 ", KNew = " ~ KNew.stringof ~
1430 ", VNew = " ~ VNew.stringof ~ "}"1431 );
1432 staticimmutableValueArraySize = 200;
1433 1434 scopearray = newMemoryDevice;
1435 scopemap = newStructhashMap!(V, K)(iterations);
1436 scopeserializer = newMapSerializer;
1437 1438 // helper to get a key from size_t1439 KinitKey ( inti )
1440 {
1441 staticif ( is ( K == struct ) )
1442 {
1443 returnK(i);
1444 }
1445 else1446 {
1447 returni;
1448 }
1449 }
1450 1451 // helper to get old key from new key1452 KfromNew ( KNewk )
1453 {
1454 staticif ( is ( K == struct ) )
1455 {
1456 returnk.old();
1457 }
1458 else1459 {
1460 returnk;
1461 }
1462 }
1463 1464 VinitVal ( inti )
1465 {
1466 staticif ( isArrayType!(V) == ArrayKind.Dynamic )
1467 {
1468 aliasElementTypeOf!(V) VA;
1469 1470 autor = newVA[ValueArraySize];
1471 1472 foreach ( refe; r )
1473 {
1474 e = VA(i);
1475 }
1476 1477 returnr;
1478 }
1479 elsereturnV(i);
1480 }
1481 1482 // Fill test map1483 for ( inti = 0; i < iterations; ++i )
1484 {
1485 *map.put(initKey(i)) = initVal(i);
1486 }
1487 1488 scopeadder = ( voiddelegate ( refK, refV ) add )
1489 {
1490 foreach ( refk, refv; map )
1491 {
1492 add(k, v);
1493 }
1494 };
1495 1496 // Dump test map (to memory)1497 staticif ( custom_dump.length > 0 )
1498 {
1499 mixin(custom_dump);
1500 }
1501 else1502 {
1503 serializer.buffered_output.output(array);
1504 serializer.dumpInternal!(K, V)(serializer.buffered_output, adder);
1505 1506 autoheader_size = MapSerializer.FileHeader!(K, V, MapSerializer.HEADER_VERSION).sizeof;
1507 }
1508 1509 // Check size of dump1510 staticif ( 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 else1519 {
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 function1526 size_tamount_loaded = 0;
1527 scopechecker = ( refKNewk, refVNewv )
1528 {
1529 amount_loaded++;
1530 staticif ( 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 else1539 {
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 unittest1555 {
1556 staticimmutableIterations = 10_000;
1557 1558 staticimmutableold_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 staticimmutableversion4_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 staticstructTestNoVersion1573 {
1574 longi;
1575 1576 staticTestNoVersionopCall ( longi )
1577 {
1578 TestNoVersiont;
1579 t.i = i*2;
1580 returnt;
1581 }
1582 1583 boolcompare ( TestNoVersion* other )
1584 {
1585 returni == other.i;
1586 }
1587 }
1588 1589 staticstructTest11590 {
1591 enumStructVersion = 0;
1592 1593 longi;
1594 }
1595 1596 staticstructTest21597 {
1598 enumStructVersion = 1;
1599 aliasTest1StructPrevious;
1600 1601 longi;
1602 longo;
1603 staticvoidconvert_o ( refTest1t, refTest2dst ) { dst.o = t.i+1; }
1604 1605 boolcompare ( Test1* old )
1606 {
1607 returnold.i == i && old.i+1 == o;
1608 }
1609 1610 boolcompare ( Test2* old )
1611 {
1612 return *old == this;
1613 }
1614 }
1615 1616 staticstructOldStruct1617 {
1618 enumStructVersion = 0;
1619 1620 intold;
1621 1622 boolcompare ( OldStruct * o )
1623 {
1624 return *o == this;
1625 }
1626 }
1627 1628 staticstructNewStruct1629 {
1630 enumStructVersion = 1;
1631 aliasOldStructStructPrevious;
1632 1633 intold;
1634 1635 inta_bit_newer;
1636 staticvoidconvert_a_bit_newer ( refOldStruct, refNewStructdst )
1637 {
1638 dst.a_bit_newer = dst.old+1;
1639 }
1640 1641 boolcompare ( OldStruct* old )
1642 {
1643 returnold.old == this.old &&
1644 old.old+1 == a_bit_newer;
1645 }
1646 }
1647 1648 staticstructOldKey1649 {
1650 enumStructVersion = 0;
1651 1652 intold2;
1653 1654 boolcompare ( OldKey * o )
1655 {
1656 return *o == this;
1657 }
1658 1659 OldKeyold ( )
1660 {
1661 returnthis;
1662 }
1663 }
1664 1665 staticstructNewKey1666 {
1667 enumStructVersion = 1;
1668 aliasOldKeyStructPrevious;
1669 1670 intold1;
1671 1672 staticvoidconvert_old1 ( refOldKeyo, refNewKeydst )
1673 {
1674 dst.old1 = o.old2;
1675 }
1676 1677 intnewer;
1678 1679 staticvoidconvert_newer ( refOldKeyo, refNewKeydst )
1680 {
1681 dst.newer = o.old2+1;
1682 }
1683 1684 boolcompare ( OldKey * oldk )
1685 {
1686 returnoldk.old2 == old1 && oldk.old2+1 == newer;
1687 }
1688 1689 OldKeyold ( )
1690 {
1691 returnOldKey(old1);
1692 }
1693 }
1694 1695 staticstructNewerKey1696 {
1697 enumStructVersion = 2;
1698 aliasNewKeyStructPrevious;
1699 1700 intold1;
1701 intwops;
1702 1703 staticvoidconvert_wops ( refNewKeyk, refNewerKeydst )
1704 {
1705 dst.wops = k.old1;
1706 }
1707 1708 boolcompare ( NewKey * n )
1709 {
1710 returnn.old1 == old1 && wops == n.old1;
1711 }
1712 1713 OldKeyold ( )
1714 {
1715 returnOldKey(old1);
1716 }
1717 }
1718 1719 staticstructNewerStruct1720 {
1721 enumStructVersion = 2;
1722 aliasNewStructStructPrevious;
1723 1724 intold;
1725 longof;
1726 1727 staticvoidconvert_of ( refNewStructn, refNewerStructdst )
1728 {
1729 dst.of = n.a_bit_newer;
1730 }
1731 1732 boolcompare ( OldStruct * olds )
1733 {
1734 returnolds.old == old;
1735 }
1736 }
1737 1738 staticstructNoPrevious1739 {
1740 enumStructVersion = 42;
1741 1742 inthello;
1743 1744 boolcompare ( NoPrevious* olds )
1745 {
1746 returnolds.hello == hello;
1747 }
1748 }
1749 1750 staticstructSinglePrevious1751 {
1752 enumStructVersion = 42;
1753 inthello42;
1754 1755 staticvoidconvert_hello42 ( refStructPreviousp, refSinglePreviousdst )
1756 {
1757 dst.hello42 = p.hello + 42;
1758 }
1759 1760 boolcompare ( StructPrevious* olds )
1761 {
1762 returnthis.hello42 == (olds.hello + 42);
1763 }
1764 1765 staticstructStructPrevious1766 {
1767 enumStructVersion = 41;
1768 aliasSinglePreviousStructNext;
1769 inthello;
1770 1771 boolcompare ( StructNext* news )
1772 {
1773 returnthis.hello == (news.hello42 - 42);
1774 }
1775 }
1776 }
1777 1778 1779 // Test creation of a SerializingMap instance1780 classHashingSerializingMap : SerializingMap!(int,int)
1781 {
1782 publicthis ( size_tn, floatload_factor = 0.75 )
1783 {
1784 super(n, load_factor);
1785 }
1786 1787 override:
1788 mixinStandardHash.toHash!(int);
1789 }
1790 1791 1792 // Test same and old version1793 testCombination!(hash_t, Test1, hash_t, Test2)(Iterations);
1794 testCombination!(hash_t, Test2, hash_t, Test2)(Iterations);
1795 1796 // Test Arrays1797 testCombination!(hash_t, Test2[], hash_t, Test2[])(Iterations);
1798 1799 // Test unversioned structs1800 testCombination!(hash_t, TestNoVersion, hash_t, TestNoVersion)(Iterations);
1801 1802 // Test old versions1803 testCombination!(hash_t, TestNoVersion, hash_t, TestNoVersion, old_load_code)(Iterations);
1804 1805 // Test conversion of old files to new ones1806 testCombination!(hash_t, OldStruct, hash_t, NewStruct, old_load_code)(Iterations);
1807 1808 // Test conversion of old files with1809 // different key versions to new ones1810 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 header1819 1820 // Test old versions1821 testCombination!(hash_t, TestNoVersion, hash_t, TestNoVersion, version4_load_code)(Iterations);
1822 1823 // Test conversion of old files to new ones1824 testCombination!(hash_t, OldStruct, hash_t, NewStruct, version4_load_code)(Iterations);
1825 1826 // Test conversion of old files with1827 // different key versions to new ones1828 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 used1836 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 instantiated1844 structS ( ubyteV )
1845 {
1846 enumStructVersion = V;
1847 1848 // comment this out and it works1849 staticif (V == 0)
1850 aliasS!(1) StructNext;
1851 1852 staticif (V == 1)
1853 aliasS!(0) StructPrevious;
1854 }
1855 1856 unittest1857 {
1858 aliasSerializingMap!(S!(0), ulong) Map;
1859 }
1860 }
1861 1862 unittest1863 {
1864 classMap : SerializingMap!(int, int)
1865 {
1866 this ( ) { super(10); }
1867 1868 // This method has to accept `const(int)` because it overrides templated1869 // base `toHash (K) (in K key)` method and DMD demands exact qualifier1870 // match for arguments.1871 publicoverridehash_ttoHash ( inintkey ) { returnkey; }
1872 }
1873 1874 scopedevice = newMemoryDevice;
1875 automap = newMap;
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 }