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.ByteArrayOutputStream;
13 
14 import hunt.io.Common;
15 import hunt.Exceptions;
16 
17 import std.algorithm;
18 import std.conv;
19 import std.format;
20 
21 /**
22  * This class implements an output stream in which the data is
23  * written into a byte array. The buffer automatically grows as data
24  * is written to it.
25  * The data can be retrieved using <code>toByteArray()</code> and
26  * <code>toString()</code>.
27  * <p>
28  * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
29  * this class can be called after the stream has been closed without
30  * generating an <tt>IOException</tt>.
31  */
32 
33 class ByteArrayOutputStream : OutputStream {
34 
35     /**
36      * The buffer where data is stored.
37      */
38     protected byte[] buf;
39 
40     /**
41      * The number of valid bytes in the buffer.
42      */
43     protected int count;
44 
45     /**
46      * Creates a new byte array output stream. The buffer capacity is
47      * initially 32 bytes, though its size increases if necessary.
48      */
49     this() {
50         this(32);
51     }
52 
53     /**
54      * Creates a new byte array output stream, with a buffer capacity of
55      * the specified size, in bytes.
56      *
57      * @param   size   the initial size.
58      * @exception  IllegalArgumentException if size is negative.
59      */
60     this(size_t size) {
61         buf = new byte[size];
62     }
63 
64     /**
65      * Increases the capacity if necessary to ensure that it can hold
66      * at least the number of elements specified by the minimum
67      * capacity argument.
68      *
69      * @param minCapacity the desired minimum capacity
70      * @throws OutOfMemoryError if {@code minCapacity < 0}.  This is
71      * interpreted as a request for the unsatisfiably large capacity
72      * {@code (long) Integer.MAX_VALUE + (minCapacity - Integer.MAX_VALUE)}.
73      */
74     private void ensureCapacity(size_t minCapacity) {
75         // overflow-conscious code
76         if (minCapacity > buf.length)
77             grow(minCapacity);
78     }
79 
80     /**
81      * The maximum size of array to allocate.
82      * Some VMs reserve some header words in an array.
83      * Attempts to allocate larger arrays may result in
84      * OutOfMemoryError: Requested array size exceeds VM limit
85      */
86     private enum int MAX_ARRAY_SIZE = int.max - 8;
87 
88     /**
89      * Increases the capacity to ensure that it can hold at least the
90      * number of elements specified by the minimum capacity argument.
91      *
92      * @param minCapacity the desired minimum capacity
93      */
94     private void grow(size_t minCapacity) {
95         // overflow-conscious code
96         size_t oldCapacity = buf.length;
97         size_t newCapacity = oldCapacity << 1;
98         if (newCapacity < minCapacity)
99             newCapacity = minCapacity;
100         if (newCapacity > MAX_ARRAY_SIZE)
101             newCapacity = hugeCapacity(minCapacity);
102         byte[] newBuf = new byte[newCapacity];
103         newBuf[0..count] = buf[0 .. count];
104         buf = newBuf;
105     }
106 
107     private static size_t hugeCapacity(size_t minCapacity) {
108         // if (minCapacity < 0) // overflow
109         //     throw new OutOfMemoryError();
110         return (minCapacity > MAX_ARRAY_SIZE) ?
111             size_t.max :
112             MAX_ARRAY_SIZE;
113     }
114 
115     alias write = OutputStream.write;
116 
117     /**
118      * Writes the specified byte to this byte array output stream.
119      *
120      * @param   b   the byte to be written.
121      */
122     override void write(int b) {
123         ensureCapacity(count + 1);
124         buf[count] = cast(byte) b;
125         count += 1;
126     }
127 
128     /**
129      * Writes <code>len</code> bytes from the specified byte array
130      * starting at offset <code>off</code> to this byte array output stream.
131      *
132      * @param   b     the data.
133      * @param   off   the start offset in the data.
134      * @param   len   the number of bytes to write.
135      */
136     override void write(byte[] b, int off, int len) {
137         if ((off < 0) || (off > b.length) || (len < 0) ||
138             ((off + len) > b.length)) {
139                 string msg = format("buffer error, size: %d, offset: %d, length: %d",
140                     b.length, off, len);
141             throw new IndexOutOfBoundsException(msg);
142         }
143         ensureCapacity(count + len);
144         buf[count .. count+len] = b[off .. off + len];
145         count += len;
146     }
147 
148     /**
149      * Writes the complete contents of this byte array output stream to
150      * the specified output stream argument, as if by calling the output
151      * stream's write method using <code>out.write(buf, 0, count)</code>.
152      *
153      * @param      out   the output stream to which to write the data.
154      * @exception  IOException  if an I/O error occurs.
155      */
156     void writeTo(OutputStream o) {
157         o.write(buf, 0, count);
158     }
159 
160     /**
161      * Resets the <code>count</code> field of this byte array output
162      * stream to zero, so that all currently accumulated output in the
163      * output stream is discarded. The output stream can be used again,
164      * reusing the already allocated buffer space.
165      *
166      * @see     hunt.io.ByteArrayInputStream#count
167      */
168     void reset() {
169         count = 0;
170     }
171 
172     /**
173      * Creates a newly allocated byte array. Its size is the current
174      * size of this output stream and the valid contents of the buffer
175      * have been copied into it.
176      *
177      * @return  the current contents of this output stream, as a byte array.
178      * @see     hunt.io.ByteArrayOutputStream#size()
179      */
180     byte[] toByteArray() {
181         // FIXME: Needing refactor or cleanup -@zxp at 7/25/2018, 10:02:15 AM
182         // 
183         return buf[0 .. count]; // .dup; // Arrays.copyOf(buf, count);
184     }
185 
186     byte[] getBuffer() {
187 		return buf;
188 	}
189 
190     /**
191      * Returns the current size of the buffer.
192      *
193      * @return  the value of the <code>count</code> field, which is the number
194      *          of valid bytes in this output stream.
195      * @see     hunt.io.ByteArrayOutputStream#count
196      */
197     int size() {
198         return count;
199     }
200 
201     /**
202      * Converts the buffer's contents into a string decoding bytes using the
203      * platform's default character set. The length of the new <tt>string</tt>
204      * is a function of the character set, and hence may not be equal to the
205      * size of the buffer.
206      *
207      * <p> This method always replaces malformed-input and unmappable-character
208      * sequences with the default replacement string for the platform's
209      * default character set. The {@linkplain java.nio.charset.CharsetDecoder}
210      * class should be used when more control over the decoding process is
211      * required.
212      *
213      * @return string decoded from the buffer's contents.
214      * @since  JDK1.1
215      */
216     override string toString() {
217         return cast(string)(buf[0 .. count]);
218     }
219 
220     /**
221      * Converts the buffer's contents into a string by decoding the bytes using
222      * the named {@link java.nio.charset.Charset charset}. The length of the new
223      * <tt>string</tt> is a function of the charset, and hence may not be equal
224      * to the length of the byte array.
225      *
226      * <p> This method always replaces malformed-input and unmappable-character
227      * sequences with this charset's default replacement string. The {@link
228      * java.nio.charset.CharsetDecoder} class should be used when more control
229      * over the decoding process is required.
230      *
231      * @param      charsetName  the name of a supported
232      *             {@link java.nio.charset.Charset charset}
233      * @return     string decoded from the buffer's contents.
234      * @exception  UnsupportedEncodingException
235      *             If the named charset is not supported
236      * @since      JDK1.1
237      */
238     string toString(string charsetName) {
239         return cast(string)(buf[0..count]); // , charsetName);
240     }
241 
242     /**
243      * Creates a newly allocated string. Its size is the current size of
244      * the output stream and the valid contents of the buffer have been
245      * copied into it. Each character <i>c</i> in the resulting string is
246      * constructed from the corresponding element <i>b</i> in the byte
247      * array such that:
248      * <blockquote><pre>
249      *     c == (char)(((hibyte &amp; 0xff) &lt;&lt; 8) | (b &amp; 0xff))
250      * </pre></blockquote>
251      *
252      * @deprecated This method does not properly convert bytes into characters.
253      * As of JDK&nbsp;1.1, the preferred way to do this is via the
254      * <code>toString(string enc)</code> method, which takes an encoding-name
255      * argument, or the <code>toString()</code> method, which uses the
256      * platform's default character encoding.
257      *
258      * @param      hibyte    the high byte of each resulting Unicode character.
259      * @return     the current contents of the output stream, as a string.
260      * @see        hunt.io.ByteArrayOutputStream#size()
261      * @see        hunt.io.ByteArrayOutputStream#toString(string)
262      * @see        hunt.io.ByteArrayOutputStream#toString()
263      */
264     // @Deprecated
265     // string toString(int hibyte) {
266     //     return new string(buf, hibyte, 0, count);
267     // }
268 
269     /**
270      * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
271      * this class can be called after the stream has been closed without
272      * generating an <tt>IOException</tt>.
273      */
274     // void close() {
275     // }
276 
277 }