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 }