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