1 /*******************************************************************************
2 
3     Simulates a device that you can write to and read from, behaves pretty much
4     like a file
5 
6     This was created as an alternative to ocean.io.device.Array, whose write()
7     function has the unreasonable limitation of always appending instead of
8     respecting the current seek position and thus not properly simulating a file
9 
10     Copyright:
11         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
12         All rights reserved.
13 
14     License:
15         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
16         Alternatively, this file may be distributed under the terms of the Tango
17         3-Clause BSD License (see LICENSE_BSD.txt for details).
18 
19 *******************************************************************************/
20 
21 module ocean.io.device.MemoryDevice;
22 
23 import ocean.meta.types.Qualifiers;
24 import ocean.io.model.IConduit;
25 
26 import core.stdc.string : memmove;
27 
28 /*******************************************************************************
29 
30     Simulates a device that you can write to and read from, behaves pretty much
31     like a file
32 
33 *******************************************************************************/
34 
35 class MemoryDevice : IConduit
36 {
37     /***************************************************************************
38 
39         Buffer to keep the data
40 
41     ***************************************************************************/
42 
43     private ubyte[] data;
44 
45     /***************************************************************************
46 
47         Current read/write position
48 
49     ***************************************************************************/
50 
51     private size_t position;
52 
53     /***************************************************************************
54 
55         Returns:
56             Return the current size of the buffer
57 
58     ***************************************************************************/
59 
60     override size_t bufferSize ( )
61     {
62         return data.length;
63     }
64 
65     /***************************************************************************
66 
67         Returns:
68             Current buffer as string
69 
70     ***************************************************************************/
71 
72     public const(void)[] peek ()
73     {
74         return data;
75     }
76 
77     /***************************************************************************
78 
79         Returns:
80             Name of this IConduit
81 
82     ***************************************************************************/
83 
84     override istring toString ( )
85     {
86         return MemoryDevice.stringof;
87     }
88 
89     /***************************************************************************
90 
91         Implemented because interfaces demand it, returns always true
92 
93     ***************************************************************************/
94 
95     override bool isAlive ( )
96     {
97         return true;
98     }
99 
100     /***************************************************************************
101 
102         Implemented because interfaces demand it, does nothing
103 
104     ***************************************************************************/
105 
106     override void detach ( ) {}
107 
108     /***************************************************************************
109 
110         Throws an exception
111 
112         Params:
113             msg = message to use in the exception
114 
115     ***************************************************************************/
116 
117     override void error ( istring msg )
118     {
119         throw new Exception ( msg );
120     }
121 
122     /***************************************************************************
123 
124         Write into this device, starting at the current seek position
125 
126         Params:
127             src = data to write
128 
129         Returns:
130             amount of written data, always src.length
131 
132     ***************************************************************************/
133 
134     override size_t write ( const(void)[] src )
135     {
136         if ( this.position + src.length > this.data.length )
137         {
138             this.data.length = this.position + src.length;
139         }
140 
141         memmove(&this.data[this.position], src.ptr, src.length);
142 
143         this.position += src.length;
144 
145         return src.length;
146     }
147 
148     /***************************************************************************
149 
150         Copies src into this stream, overwriting any existing data
151 
152         Params:
153             src = stream to copy from
154             max = amount of bytes to copy, -1 means infinite
155 
156         Returns:
157             this class for chaining
158 
159     ***************************************************************************/
160 
161     override OutputStream copy ( InputStream src, size_t max = -1 )
162     {
163         size_t len;
164 
165         if ( max == -1 )
166         {
167             len = max > this.data.length ? this.data.length : max;
168         }
169         else
170         {
171             len = this.data.length;
172         }
173 
174         this.position = 0;
175 
176         auto new_data = src.load();
177 
178         this.data.length = new_data.length;
179         this.data[] = cast(ubyte[])new_data[];
180 
181         return this;
182     }
183 
184     /***************************************************************************
185 
186         Returns:
187             the stream this stream writes to, which is none, so always null
188 
189     ***************************************************************************/
190 
191     override OutputStream output ( )
192     {
193         return null;
194     }
195 
196     /***************************************************************************
197 
198         Reads into dst, starting to read from the current seek position
199 
200         Params:
201             dst = array to read into
202 
203         Returns:
204             Eof (and no action) if current seek position is at the end of the
205             buffer,
206             Else the amount of bytes read.
207 
208     ***************************************************************************/
209 
210     override size_t read ( void[] dst )
211     {
212         if ( this.position < this.data.length )
213         {
214             auto end_pos = this.position + dst.length > this.data.length ?
215                                 this.data.length : this.position + dst.length;
216 
217             dst[0..end_pos-this.position] = this.data[this.position .. end_pos];
218 
219             scope(exit) this.position = end_pos;
220 
221             return end_pos - this.position;
222         }
223         else
224         {
225             return IConduit.Eof;
226         }
227     }
228 
229     /***************************************************************************
230 
231         Params:
232             max = maximum amount of bytes to return
233 
234         Returns:
235             slice to the internal buffer up to max bytes (-1 means infinite,
236             thus the whole buffer)
237 
238     ***************************************************************************/
239 
240     override void[] load ( size_t max = -1 )
241     {
242         size_t len;
243 
244         if ( max == -1 )
245         {
246             len = max > this.data.length ? this.data.length : max;
247         }
248         else
249         {
250             len = this.data.length;
251         }
252 
253         return this.data[0..len];
254     }
255 
256     /***************************************************************************
257 
258         Returns:
259             This streams input stream, alas this stream has no input stream,
260             so null
261 
262     ***************************************************************************/
263 
264     override InputStream input ( )
265     {
266         return null;
267     }
268 
269     /***************************************************************************
270 
271         Change the internal seeker position
272 
273         Params:
274             offset = amount of bytes to add to the anchor to seek the new
275                      position
276             anchor = specifies which point should be used as basis for the
277                      offset
278 
279         Returns:
280             the new seeker position
281 
282     ***************************************************************************/
283 
284     override long seek ( long offset, Anchor anchor = Anchor.Begin )
285     {
286         with ( Anchor ) switch ( anchor )
287         {
288             case Begin:
289                 break;
290             case Current:
291                 offset = this.position + offset;
292                 break;
293             case End:
294                 offset = this.data.length + offset;
295                 break;
296             default:
297                 assert(false);
298         }
299 
300         if ( offset > this.data.length )
301         {
302             return this.position = this.data.length;
303         }
304 
305         return this.position = offset;
306     }
307 
308     /***************************************************************************
309 
310         Used by FormatOutput and other streams.
311 
312     ***************************************************************************/
313 
314     override IConduit conduit ()
315     {
316         return this;
317     }
318 
319     /***************************************************************************
320 
321         Does nothing
322 
323     ***************************************************************************/
324 
325     override IOStream flush ( )
326     {
327         return this;
328     }
329 
330     /***************************************************************************
331 
332         Deletes the buffer and resets position
333 
334     ***************************************************************************/
335 
336     override void close ( )
337     {
338         import core.memory;
339         GC.free(this.data.ptr);
340         this.data = null;
341         this.position = 0;
342     }
343 }
344 
345 version (unittest)
346 {
347     import ocean.core.Test;
348 }
349 
350 unittest
351 {
352     auto m = new MemoryDevice;
353 
354     auto data = "This is a string";
355 
356     auto dst = new void[data.length];
357 
358     test!("==")(m.position, 0);
359     m.write(data);
360     test(m.data == cast(ubyte[])data);
361     test!("==")(m.position, data.length);
362 
363     m.seek(0);
364     m.read(dst);
365     test(dst == data);
366     test!("==")(m.position, data.length);
367 
368     m.seek(0);
369     m.write(data);
370     test(m.data == cast(ubyte[])data);
371     test!("==")(m.position, data.length);
372 }