1 /*******************************************************************************
2 
3     Simple serializer for reading / writing generic data from / to IOStreams
4 
5     Usage example, writing:
6 
7     ---
8 
9         import ocean.io.serialize.SimpleSerializer;
10 
11         scope file = new File("myfile.dat", File.WriteCreate);
12 
13         char[] some_data = "data to be written to the file first";
14         char[][] more_data = ["second", "third", "fourth", "etc"];
15 
16         SimpleSerializer.write(file, some_data);
17         SimpleSerializer.write(file, more_data);
18 
19     ---
20 
21     Usage example, reading:
22 
23     ---
24 
25         import ocean.io.serialize.SimpleSerializer;
26 
27         scope file = new File("myfile.dat", File.ReadExisting);
28 
29         char[] some_data;
30         char[][] more_data;
31 
32         SimpleSerializer.read(file, some_data);
33         SimpleSerializer.read(file, more_data);
34 
35     ---
36 
37     Copyright:
38         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
39         All rights reserved.
40 
41     License:
42         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
43         Alternatively, this file may be distributed under the terms of the Tango
44         3-Clause BSD License (see LICENSE_BSD.txt for details).
45 
46 *******************************************************************************/
47 
48 module ocean.io.serialize.SimpleStreamSerializer;
49 
50 
51 
52 
53 import ocean.meta.types.Qualifiers;
54 
55 import ocean.core.Enforce: enforce;
56 
57 import ocean.meta.traits.Basic /* : isArrayType, ArrayKind */;
58 
59 import ocean.io.model.IConduit: IOStream, InputStream, OutputStream;
60 
61 
62 public alias SimpleStreamSerializerT!(true) SimpleStreamSerializerArrays;
63 public alias SimpleStreamSerializerT!(false) SimpleStreamSerializer;
64 
65 /*******************************************************************************
66 
67     Simple serializer struct - just a namespace, all methods are static.
68 
69     Params:
70         SerializeDynArrays = true: dynamic arrays in structs will be serialized
71                              false: not.
72 
73 *******************************************************************************/
74 
75 struct SimpleStreamSerializerT ( bool SerializeDynArrays = true )
76 {
77 static:
78 
79     /***************************************************************************
80 
81         Writes something to an output stream. Single elements are written
82         straight to the output stream, while array types have their length
83         written, followed by each element.
84 
85         If data is a pointer to a struct or union, it is dereferenced
86         automatically.
87 
88         Params:
89             T = type of data to write
90             output = output stream to write to
91             data = data to write
92 
93         Returns:
94             number of bytes transmitted
95 
96         Throws:
97             EofException on End Of Flow condition (note that the exception is
98             always newed)
99 
100     ***************************************************************************/
101 
102     public size_t write ( T ) ( OutputStream output, T data )
103     {
104         return transmit(output, data);
105     }
106 
107     /***************************************************************************
108 
109         Writes data to output, consuming the data buffer content to its
110         entirety.
111 
112         Params:
113             output = stream to write to
114             data = pointer to data buffer
115             bytes = length of data in bytes
116 
117         Returns:
118             number of bytes transmitted
119 
120         Throws:
121             EofException on End Of Flow condition (note that the exception is
122             always newed)
123 
124     ***************************************************************************/
125 
126     public size_t writeData ( OutputStream output, in void* data, size_t bytes )
127     {
128         return writeData(output, data[0..bytes]);
129     }
130 
131     /***************************************************************************
132 
133         Writes data to output, consuming the data buffer content to its
134         entirety.
135 
136         Params:
137             output = stream to write to
138             data = data buffer
139 
140         Returns:
141             number of bytes transmitted
142 
143         Throws:
144             EofException on End Of Flow condition (note that the exception is
145             always newed)
146 
147     ***************************************************************************/
148 
149     public size_t writeData ( OutputStream output, in void[] data )
150     {
151         size_t transmitted = 0;
152 
153         while (transmitted < data.length)
154         {
155             size_t ret = output.write(data[transmitted .. $]);
156 
157             enforce!(EofException)(ret != output.Eof, "end of flow while "
158                 ~ "writing '" ~ output.conduit.toString() ~ "'");
159 
160             transmitted += ret;
161         }
162 
163         return transmitted;
164     }
165 
166     /***************************************************************************
167 
168         Reads something from an input stream. Single elements are read straight
169         from the input stream, while array types have their length read,
170         followed by each element.
171 
172         If data is a pointer to a struct or union, it is dereferenced
173         automatically.
174 
175         Params:
176             T = type of data to read
177             input = input stream to read from
178             data = data to read
179 
180         Returns:
181             number of bytes transmitted
182 
183         Throws:
184             EofException on End Of Flow condition (note that the exception is
185             always newed)
186 
187     ***************************************************************************/
188 
189     public size_t read ( T ) ( InputStream input, ref T data )
190     {
191         return transmit(input, data);
192     }
193 
194     /***************************************************************************
195 
196         Reads data from input, populating the data buffer to its entirety.
197 
198         Params:
199             input = stream to read from
200             data = pointer to data buffer
201             bytes = length of data in bytes
202 
203         Returns:
204             number of bytes transmitted
205 
206         Throws:
207             EofException on End Of Flow condition (note that the exception is
208             always newed)
209 
210     ***************************************************************************/
211 
212     public size_t readData ( InputStream input, void* data, size_t bytes )
213     {
214         return readData(input, data[0..bytes]);
215     }
216 
217     /***************************************************************************
218 
219         Reads data from input, populating the data buffer to its entirety.
220 
221         Params:
222             input = stream to read from
223             data = data buffer
224 
225         Returns:
226             number of bytes transmitted
227 
228         Throws:
229             EofException on End Of Flow condition (note that the exception is
230             always newed)
231 
232     ***************************************************************************/
233 
234     public size_t readData ( InputStream input, void[] data )
235     {
236         size_t transmitted = 0;
237 
238         while (transmitted < data.length)
239         {
240             size_t ret = input.read(data[transmitted .. $]);
241 
242             enforce!(EofException)(ret != input.Eof, "end of flow while " ~
243                 "reading '" ~ input.conduit.toString() ~ "'");
244 
245             transmitted += ret;
246         }
247 
248         return transmitted;
249     }
250 
251     /***************************************************************************
252 
253         Reads/writes something from/to an io stream. Single elements are
254         transmitted straight to the stream, while array types have their length
255         transmitted, followed by each element.
256 
257         If data is a pointer to a struct or union, it is dereferenced
258         automatically.
259 
260         Params:
261             Stream = type of stream; must be either InputStream or OutputStream
262             T = type of data to transmit
263             stream = stream to read from / write to
264             data = data to transmit
265 
266         Returns:
267             number of bytes transmitted
268 
269         Throws:
270             EofException on End Of Flow condition (note that the exception is
271             always newed)
272 
273     ***************************************************************************/
274 
275     public size_t transmit ( Stream : IOStream, T ) ( Stream stream, ref T data )
276     {
277         size_t transmitted = 0;
278 
279         static if ( is(T A : A[]) )
280         {
281             // transmit array length
282             static if ( is(Stream : OutputStream) )
283             {
284                 size_t length = data.length;
285                 transmitted += transmit(stream, length);
286             }
287             else
288             {
289                 static assert ( is(Stream : InputStream),
290                     "stream must be either InputStream or OutputStream, "
291                     ~ "not '" ~ Stream.stringof ~ '\'' );
292 
293                 size_t length;
294                 transmitted += transmit(stream, length);
295                 data.length = length;
296                 assumeSafeAppend(data);
297             }
298 
299             // recursively transmit arrays of arrays
300             static if ( is(A B == B[]) )
301             {
302                 foreach ( ref d; data )
303                 {
304                     transmitted += transmit(stream, d);
305                 }
306             }
307             else
308             {
309                 transmitted += transmitArrayData(stream, data);
310             }
311         }
312         else static if (is (T A == A*) && (is (A == struct) || is (A == union)))
313         {
314             transmitted += transmitData(stream, data, A.sizeof);
315         }
316         // Handle structs with arrays if enabled
317         else static if ( is ( T == struct ) && SerializeDynArrays )
318         {
319             foreach ( i, field; data.tupleof )
320             {
321                 static if ( isArrayType!(typeof(field)) == ArrayKind.Static )
322                 {
323                     transmitted += transmitData(stream,
324                                                 cast(void*)&data.tupleof[i],
325                                                 typeof(field).sizeof);
326                 }
327                 else
328                 {
329                     transmitted += transmit(stream, data.tupleof[i]);
330                 }
331             }
332         }
333         // handle everything else, including structs if arrays are disabled
334         else
335         {
336             transmitted += transmitData(stream, cast(void*)&data, T.sizeof);
337         }
338 
339         return transmitted;
340     }
341 
342     /***************************************************************************
343 
344         Reads/writes data from/to an io stream, populating/consuming
345         data[0 .. bytes].
346 
347         Params:
348             Stream = type of stream; must be either InputStream or OutputStream
349             stream = stream to read from / write to
350             data   = pointer to data buffer
351             bytes  = data buffer length (bytes)
352 
353         Returns:
354             number of bytes transmitted
355 
356         Throws:
357             EofException on End Of Flow condition (note that the exception is
358             always newed)
359 
360     ***************************************************************************/
361 
362     public size_t transmitData ( Stream : IOStream ) ( Stream stream, void* data,
363         size_t bytes )
364     {
365         return transmitData(stream, data[0 .. bytes]);
366     }
367 
368     /***************************************************************************
369 
370         Reads/writes data from/to an io stream, populating/consuming data to its
371         entirety.
372 
373         Params:
374             Stream = type of stream; must be either InputStream or OutputStream
375             stream = stream to read from / write to
376             data = pointer to data buffer
377 
378         Returns:
379             number of bytes transmitted
380 
381         Throws:
382             EofException on End Of Flow condition (note that the exception is
383             always newed)
384 
385     ***************************************************************************/
386 
387     public size_t transmitData ( Stream : IOStream ) ( Stream stream, void[] data )
388     {
389         static if ( is(Stream : OutputStream) )
390         {
391             static assert (!is(Stream : InputStream), "stream is '" ~
392                 Stream.stringof ~  "; please cast it either to InputStream " ~
393                 "or OutputStream" );
394             return writeData(stream, data);
395         }
396         else
397         {
398             static assert (is(Stream : InputStream),
399                 "stream must be either InputStream or OutputStream, not '" ~
400                 Stream.stringof ~ '\'' );
401             return readData(stream, data);
402         }
403     }
404 
405     /***************************************************************************
406 
407         Reads/writes the content of array from/to stream, populating array to
408         its entirety.
409 
410         Params:
411             stream = stream to read from/write to
412             array = array to transmit
413 
414         Returns:
415             number of bytes transmitted
416 
417         Throws:
418             EofException on End Of Flow condition (note that the exception is
419             always newed)
420 
421     ***************************************************************************/
422 
423     public size_t transmitArrayData ( Stream : IOStream, T = T[] )
424         ( Stream stream, T array )
425     {
426         static if ( is(T U : U[]) )
427         {
428             return transmitData(stream, cast (void*) array.ptr,
429                     array.length * U.sizeof);
430         }
431         else
432         {
433             static assert(false,
434                 "transmitArrayData cannot handle non-array type " ~ T.stringof);
435         }
436     }
437 }
438 
439 
440 /*******************************************************************************
441 
442     End Of Flow exception class, thrown when an I/O operation on an IOStream
443     results in EOF.
444 
445 *******************************************************************************/
446 
447 public class EofException : Exception
448 {
449     import ocean.core.Exception : DefaultExceptionCtor;
450 
451     mixin DefaultExceptionCtor;
452 
453     version (unittest)
454     {
455         import ocean.io.device.MemoryDevice;
456     }
457 
458     /***************************************************************************
459 
460         Test that reading from an empty conduit throws an instance of this
461         class.
462 
463     ***************************************************************************/
464 
465     unittest
466     {
467         auto f = new MemoryDevice;
468         int x;
469         testThrown!(typeof(this))(SimpleStreamSerializer.read(f, x));
470     }
471 }
472 
473 
474 version (unittest)
475 {
476     version (UnitTestVerbose) import ocean.io.Stdout;
477     import ocean.io.device.MemoryDevice;
478     import ocean.core.Test;
479 
480     void testSerialization ( T ) ( T write )
481     {
482         T read;
483 
484         scope file = new MemoryDevice;
485 
486         SimpleStreamSerializerArrays.write(file, write);
487         file.seek(0);
488 
489         SimpleStreamSerializerArrays.read(file, read);
490         version ( UnitTestVerbose ) Stdout.formatln("Wrote {} to conduit, read {}", write, read);
491         test!("==")(read, write, "Error serializing " ~ T.stringof);
492     }
493 }
494 
495 unittest
496 {
497     version (UnitTestVerbose)
498         Stdout.formatln("Running ocean.io.serialize.SimpleStreamSerializer unittest");
499 
500     uint an_int = 23;
501     testSerialization(an_int);
502 
503     mstring a_string = "hollow world".dup;
504     testSerialization(a_string);
505 
506     mstring[] a_string_array = ["hollow world".dup, "journey to the centre".dup,
507         "of the earth".dup];
508     testSerialization(a_string_array);
509 
510     // Check structs with arrays
511     {
512         struct AStruct
513         {
514             struct Another
515             {
516                 ulong first;
517                 ushort second;
518                 char[2] stat;
519             }
520 
521             Another[] arr;
522         }
523 
524         auto a_struct =
525             AStruct([AStruct.Another(1234,563, "ab"),
526                     AStruct.Another(643,53, "ec"),
527                     AStruct.Another(567,66, "ef")]);
528 
529         AStruct read;
530 
531         scope file = new MemoryDevice;
532 
533         SimpleStreamSerializerArrays.write(file, a_struct);
534         file.seek(0);
535 
536         SimpleStreamSerializerArrays.read(file, read);
537 
538         test!("==")(a_struct.arr.length, read.arr.length, "Not equal!");
539         test!("!=")(a_struct.arr.ptr, read.arr.ptr,
540                "Deserialized pointer is the same!");
541     }
542 
543     version (UnitTestVerbose) Stdout.formatln("done unittest\n");
544 }