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 }