1 /**
2  * Defines streams for reading and writing primitive data and arrays of
3  * primitive values. So-called "data" input and output streams are defined as
4  * decorators around an existing "base" stream, so when you read or write on
5  * a data stream, it's just performing an analogous operation on its base
6  * stream.
7  */
8 module streams.types.data;
9 
10 import streams.primitives;
11 import std.traits;
12 
13 struct DataInputStream(BaseStream) if (isByteInputStream!BaseStream) {
14     private BaseStream* stream;
15 
16     this(ref BaseStream stream) {
17         this.stream = &stream;
18     }
19 
20     int read(ubyte[] buffer) {
21         return this.stream.read(buffer);
22     }
23 
24     DataType read(DataType)() {
25         static if (isSomeString!DataType) {
26             return cast(DataType) readArray!(char[])();
27         } else static if (isStaticArray!DataType) {
28             return readStaticArray!DataType();
29         } else static if (isArray!DataType) {
30             return readArray!DataType();
31         } else {
32             return readPrimitive!DataType();
33         }
34     }
35 
36     private T readPrimitive(T)() {
37         union U { T value; ubyte[T.sizeof] bytes; }
38         U u;
39         int bytesRead = this.stream.read(u.bytes[]);
40         if (bytesRead != T.sizeof) {
41             import std.format;
42             throw new StreamException(format!
43                 "Failed to read value of type %s (%d bytes) from range. Read %d bytes instead."
44                 (T.stringof, T.sizeof, bytesRead)
45             );
46         }
47         return u.value;
48     }
49 
50     private T readArray(T)() if (isArray!T) {
51         uint size;
52         try {
53             size = readPrimitive!uint();
54         } catch (StreamException e) {
55             throw new StreamException("Failed to read array size uint.", e);
56         }
57         alias ElementType = typeof(*T.init.ptr);
58         T tempData = new ElementType[size];
59         for (uint i = 0; i < size; i++) {
60             try {
61                 tempData[i] = readPrimitive!ElementType();
62             } catch (StreamException e) {
63                 import std.format;
64                 throw new StreamException(format!
65                     "Failed to read array element %d of %d, of type %s."
66                     (i + 1, size, ElementType.stringof),
67                     e
68                 );
69             }
70         }
71         return tempData;
72     }
73 
74     private T readStaticArray(T)() if (isStaticArray!T) {
75         alias ElementType = typeof(*T.init.ptr);
76         T data;
77         for (uint i = 0; i < T.length; i++) {
78             try {
79                 data[i] = readPrimitive!ElementType();
80             } catch (StreamException e) {
81                 import std.format;
82                 throw new StreamException(format!
83                     "Failed to read static array element %d of %d, of type %s."
84                     (i + 1, T.length, ElementType.stringof),
85                     e
86                 );
87             }
88         }
89         return data;
90     }
91 }
92 
93 /** 
94  * Creates and returns a data input stream that's wrapped around the given
95  * byte input stream.
96  * Params:
97  *   stream = The stream to wrap in a data input stream.
98  * Returns: The data input stream.
99  */
100 DataInputStream!S dataInputStreamFor(S)(
101     ref S stream
102 ) if (isByteInputStream!S) {
103     return DataInputStream!S(stream);
104 }
105 
106 struct DataOutputStream(BaseStream) if (isByteOutputStream!BaseStream) {
107     private BaseStream* stream;
108 
109     this(ref BaseStream stream) {
110         this.stream = &stream;
111     }
112 
113     int write(ubyte[] buffer) {
114         return this.stream.write(buffer);
115     }
116 
117     void write(T)(T value) {
118         static if (isSomeString!T) {
119             writeArray!(char[])(cast(char[]) value);
120         } else static if (isStaticArray!T) {
121             writeStaticArray!T(value);
122         } else static if (isArray!T) {
123             writeArray!T(value);
124         } else {
125             writePrimitive!T(value);
126         }
127     }
128 
129     private void writePrimitive(T)(T value) {
130         union U { T value; ubyte[T.sizeof] bytes; }
131         U u;
132         u.value = value;
133         int bytesWritten = this.stream.write(u.bytes[]);
134         if (bytesWritten != T.sizeof) {
135             import std.format;
136             throw new StreamException(format!
137                 "Failed to write value of type %s (%d bytes, value = %s) to range. Wrote %d bytes instead."
138                 (T.stringof, T.sizeof, value, bytesWritten)
139             );
140         }
141     }
142 
143     private void writeArray(T)(T value) if (isArray!T) {
144         try {
145             writePrimitive!uint(cast(uint) value.length);
146         } catch (StreamException e) {
147             import std.format;
148             throw new StreamException("Failed to write array size uint.");
149         }
150         if (value.length == 0) return;
151         alias ElementType = typeof(value[0]);
152         for (uint i = 0; i < value.length; i++) {
153             try {
154                 writePrimitive!ElementType(value[i]);
155             } catch (StreamException e) {
156                 import std.format;
157                 throw new StreamException(format!
158                     "Failed to write array element %d of %d, of type %s."
159                     (i + 1, value.length, ElementType.stringof),
160                     e
161                 );
162             }
163         }
164     }
165 
166     private void writeStaticArray(T)(T value) if (isStaticArray!T) {
167         static if (T.length == 0) return;
168         alias ElementType = typeof(*T.init.ptr);
169         for (uint i = 0; i < T.length; i++) {
170             try {
171                 writePrimitive!ElementType(value[i]);
172             } catch (StreamException e) {
173                 import std.format;
174                 throw new StreamException(format!
175                     "Failed to write static array element %d of %d, of type %s."
176                     (i + 1, T.length, ElementType.stringof),
177                     e
178                 );
179             }
180         }
181     }
182 }
183 
184 /** 
185  * Creates and returns a data output stream that's wrapped around the given
186  * byte output stream.
187  * Params:
188  *   stream = The stream to wrap in a data output stream.
189  * Returns: The data output stream.
190  */
191 DataOutputStream!S dataOutputStreamFor(S)(
192     ref S stream
193 ) if (isByteOutputStream!S) {
194     return DataOutputStream!S(stream);
195 }
196 
197 unittest {
198     import streams.types.array;
199 
200     auto sOut = ArrayOutputStream!ubyte();
201     auto dataOut = dataOutputStreamFor(sOut);
202     dataOut.write(42);
203     dataOut.write(true);
204     dataOut.write("Hello");
205     ubyte[] data = sOut.toArray();
206     auto sIn = inputStreamFor(data);
207     auto dataIn = dataInputStreamFor(sIn);
208     assert(dataIn.read!int == 42);
209     assert(dataIn.read!bool == true);
210     assert(dataIn.read!string == "Hello");
211 }