1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.stream.Writer;
13 
14 import hunt.Exceptions;
15 import hunt.util.Common;
16 import hunt.util.Appendable;
17 
18 import std.algorithm;
19 import std.array;
20 import std.exception;
21 import std.conv;
22 import std.string;
23 
24 /**
25 */
26 class Writer : Appendable, Closeable, Flushable {
27 
28     /**
29      * Temporary buffer used to hold writes of strings and single characters
30      */
31     private byte[] writeBuffer;
32 
33     /**
34      * Size of writeBuffer, must be >= 1
35      */
36     private enum int WRITE_BUFFER_SIZE = 1024;
37 
38 
39     /**
40      * The object used to synchronize operations on this stream.  For
41      * efficiency, a character-stream object may use an object other than
42      * itself to protect critical sections.  A subclass should therefore use
43      * the object in this field rather than {@code this} or a synchronized
44      * method.
45      */
46     protected Object lock;
47 
48     /**
49      * Creates a new character-stream writer whose critical sections will
50      * synchronize on the writer itself.
51      */
52     protected this() {
53         this.lock = this;
54     }
55 
56     /**
57      * Creates a new character-stream writer whose critical sections will
58      * synchronize on the given object.
59      *
60      * @param  lock
61      *         Object to synchronize on
62      */
63     protected this(Object lock) {
64         if (lock is null) {
65             throw new NullPointerException();
66         }
67         this.lock = lock;
68     }
69 
70     /**
71      * Writes a single character.  The character to be written is contained in
72      * the 16 low-order bits of the given integer value; the 16 high-order bits
73      * are ignored.
74      *
75      * <p> Subclasses that intend to support efficient single-character output
76      * should override this method.
77      *
78      * @param  c
79      *         int specifying a character to be written
80      *
81      * @throws  IOException
82      *          If an I/O error occurs
83      */
84     void write(int c) {
85         synchronized (lock) {
86             write([cast(byte) c]);
87         }
88     }
89 
90     /**
91      * Writes an array of characters.
92      *
93      * @param  cbuf
94      *         Array of characters to be written
95      *
96      * @throws  IOException
97      *          If an I/O error occurs
98      */
99     void write(byte[] cbuf) {
100         write(cbuf, 0, cast(int)cbuf.length);
101     }
102 
103     /**
104      * Writes a portion of an array of characters.
105      *
106      * @param  cbuf
107      *         Array of characters
108      *
109      * @param  off
110      *         Offset from which to start writing characters
111      *
112      * @param  len
113      *         Number of characters to write
114      *
115      * @throws  IndexOutOfBoundsException
116      *          Implementations should throw this exception
117      *          if {@code off} is negative, or {@code len} is negative,
118      *          or {@code off + len} is negative or greater than the length
119      *          of the given array
120      *
121      * @throws  IOException
122      *          If an I/O error occurs
123      */
124     abstract void write(byte[] cbuf, int off, int len);
125 
126     /**
127      * Writes a string.
128      *
129      * @param  str
130      *         string to be written
131      *
132      * @throws  IOException
133      *          If an I/O error occurs
134      */
135     void write(string str) {
136         write(cast(byte[])str);
137     }
138 
139     /**
140      * Writes a portion of a string.
141      *
142      * @implSpec
143      * The implementation in this class throws an
144      * {@code IndexOutOfBoundsException} for the indicated conditions;
145      * overriding methods may choose to do otherwise.
146      *
147      * @param  str
148      *         A string
149      *
150      * @param  off
151      *         Offset from which to start writing characters
152      *
153      * @param  len
154      *         Number of characters to write
155      *
156      * @throws  IndexOutOfBoundsException
157      *          Implementations should throw this exception
158      *          if {@code off} is negative, or {@code len} is negative,
159      *          or {@code off + len} is negative or greater than the length
160      *          of the given string
161      *
162      * @throws  IOException
163      *          If an I/O error occurs
164      */
165     void write(string str, int off, int len) {
166         synchronized (lock) {
167             write(cast(byte[])str[off .. off + len]);
168         }
169     }
170 
171 
172     /**
173      * Appends the specified character sequence to this writer.
174      *
175      * <p> An invocation of this method of the form {@code out.append(csq)}
176      * behaves in exactly the same way as the invocation
177      *
178      * <pre>
179      *     out.write(csq.toString()) </pre>
180      *
181      * <p> Depending on the specification of {@code toString} for the
182      * character sequence {@code csq}, the entire sequence may not be
183      * appended. For instance, invoking the {@code toString} method of a
184      * character buffer will return a subsequence whose content depends upon
185      * the buffer's position and limit.
186      *
187      * @param  csq
188      *         The character sequence to append.  If {@code csq} is
189      *         {@code null}, then the four characters {@code "null"} are
190      *         appended to this writer.
191      *
192      * @return  This writer
193      *
194      * @throws  IOException
195      *          If an I/O error occurs
196      *
197      */
198     Writer append(const(char)[] csq) {
199         write(cast(string)csq);
200         return this;
201     }
202 
203     /**
204      * Appends a subsequence of the specified character sequence to this writer.
205      * {@code Appendable}.
206      *
207      * <p> An invocation of this method of the form
208      * {@code out.append(csq, start, end)} when {@code csq}
209      * is not {@code null} behaves in exactly the
210      * same way as the invocation
211      *
212      * <pre>{@code
213      *     out.write(csq.subSequence(start, end).toString())
214      * }</pre>
215      *
216      * @param  csq
217      *         The character sequence from which a subsequence will be
218      *         appended.  If {@code csq} is {@code null}, then characters
219      *         will be appended as if {@code csq} contained the four
220      *         characters {@code "null"}.
221      *
222      * @param  start
223      *         The index of the first character in the subsequence
224      *
225      * @param  end
226      *         The index of the character following the last character in the
227      *         subsequence
228      *
229      * @return  This writer
230      *
231      * @throws  IndexOutOfBoundsException
232      *          If {@code start} or {@code end} are negative, {@code start}
233      *          is greater than {@code end}, or {@code end} is greater than
234      *          {@code csq.length()}
235      *
236      * @throws  IOException
237      *          If an I/O error occurs
238      *
239      */
240     Writer append(const(char)[] csq, int start, int end) {
241         if (csq.empty()) csq = "null";
242         return append(csq[start .. end]);
243     }
244 
245 
246     /**
247      * Appends the specified character to this writer.
248      *
249      * <p> An invocation of this method of the form {@code out.append(c)}
250      * behaves in exactly the same way as the invocation
251      *
252      * <pre>
253      *     out.write(c) </pre>
254      *
255      * @param  c
256      *         The 16-bit character to append
257      *
258      * @return  This writer
259      *
260      * @throws  IOException
261      *          If an I/O error occurs
262      */
263     Writer append(char c) {
264         write(c);
265         return this;
266     }
267 
268     /**
269      * Flushes the stream.  If the stream has saved any characters from the
270      * various write() methods in a buffer, write them immediately to their
271      * intended destination.  Then, if that destination is another character or
272      * byte stream, flush it.  Thus one flush() invocation will flush all the
273      * buffers in a chain of Writers and OutputStreams.
274      *
275      * <p> If the intended destination of this stream is an abstraction provided
276      * by the underlying operating system, for example a file, then flushing the
277      * stream guarantees only that bytes previously written to the stream are
278      * passed to the operating system for writing; it does not guarantee that
279      * they are actually written to a physical device such as a disk drive.
280      *
281      * @throws  IOException
282      *          If an I/O error occurs
283      */
284     abstract void flush();
285 
286     /**
287      * Closes the stream, flushing it first. Once the stream has been closed,
288      * further write() or flush() invocations will cause an IOException to be
289      * thrown. Closing a previously closed stream has no effect.
290      *
291      * @throws  IOException
292      *          If an I/O error occurs
293      */
294     abstract void close();
295 }
296 
297 
298 /**
299  * Returns a new {@code Writer} which discards all characters.  The
300  * returned stream is initially open.  The stream is closed by calling
301  * the {@code close()} method.  Subsequent calls to {@code close()} have
302  * no effect.
303  *
304  * <p> While the stream is open, the {@code append(char)}, {@code
305  * append(CharSequence)}, {@code append(CharSequence, int, int)},
306  * {@code flush()}, {@code write(int)}, {@code write(char[])}, and
307  * {@code write(char[], int, int)} methods do nothing. After the stream
308  * has been closed, these methods all throw {@code IOException}.
309  *
310  * <p> The {@link #lock object} used to synchronize operations on the
311  * returned {@code Writer} is not specified.
312  *
313  * @return a {@code Writer} which discards all characters
314  *
315  */
316 class NullWriter : Writer {
317     private shared bool closed;
318 
319     private void ensureOpen() {
320         if (closed) {
321             throw new IOException("Stream closed");
322         }
323     }
324 
325     
326     override Writer append(char c) {
327         ensureOpen();
328         return this;
329     }
330 
331     override void write(int c) {
332         ensureOpen();
333     }
334 
335     override void write(byte[] cbuf, int off, int len) {
336         assert(off+len <= cbuf.length);
337         ensureOpen();
338     }
339 
340     
341     override void write(string str) {
342         assert(!str.empty());
343         ensureOpen();
344     }
345 
346     
347     override void write(string str, int off, int len) {
348         assert(off+len <= str.length);
349         ensureOpen();
350     }
351 
352     
353     override void flush() {
354         ensureOpen();
355     }
356 
357     
358     override void close() {
359         closed = true;
360     }
361 }
362 
363 /**
364     Helper for Writer to create a null writer.*/
365 Writer nullWriter() {
366     return new NullWriter();
367 }