1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.io.output;
18
19 import static org.apache.commons.io.IOUtils.EOF;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.io.SequenceInputStream;
25 import java.io.UnsupportedEncodingException;
26 import java.nio.charset.Charset;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30
31 import org.apache.commons.io.Charsets;
32 import org.apache.commons.io.IOUtils;
33 import org.apache.commons.io.input.ClosedInputStream;
34
35 /**
36 * This is the base class for implementing an output stream in which the data
37 * is written into a byte array. The buffer automatically grows as data
38 * is written to it.
39 * <p>
40 * The data can be retrieved using {@code toByteArray()} and
41 * {@code toString()}.
42 * Closing an {@link AbstractByteArrayOutputStream} has no effect. The methods in
43 * this class can be called after the stream has been closed without
44 * generating an {@link IOException}.
45 * </p>
46 * <p>
47 * This is the base for an alternative implementation of the
48 * {@link java.io.ByteArrayOutputStream} class. The original implementation
49 * only allocates 32 bytes at the beginning. As this class is designed for
50 * heavy duty it starts at {@value #DEFAULT_SIZE} bytes. In contrast to the original it doesn't
51 * reallocate the whole memory block but allocates additional buffers. This
52 * way no buffers need to be garbage collected and the contents don't have
53 * to be copied to the new buffer. This class is designed to behave exactly
54 * like the original. The only exception is the deprecated
55 * {@link java.io.ByteArrayOutputStream#toString(int)} method that has been
56 * ignored.
57 * </p>
58 *
59 * @param <T> The AbstractByteArrayOutputStream subclass
60 * @since 2.7
61 */
62 public abstract class AbstractByteArrayOutputStream<T extends AbstractByteArrayOutputStream<T>> extends OutputStream {
63
64 /**
65 * Constructor for an InputStream subclass.
66 *
67 * @param <T> the type of the InputStream.
68 */
69 @FunctionalInterface
70 protected interface InputStreamConstructor<T extends InputStream> {
71
72 /**
73 * Constructs an InputStream subclass.
74 *
75 * @param buffer the buffer.
76 * @param offset the offset into the buffer.
77 * @param length the length of the buffer.
78 * @return the InputStream subclass.
79 */
80 T construct(byte[] buffer, int offset, int length);
81 }
82
83 static final int DEFAULT_SIZE = 1024;
84
85 /** The list of buffers, which grows and never reduces. */
86 private final List<byte[]> buffers = new ArrayList<>();
87
88 /** The total count of bytes written. */
89 protected int count;
90
91 /** The current buffer. */
92 private byte[] currentBuffer;
93
94 /** The index of the current buffer. */
95 private int currentBufferIndex = -1;
96
97 /** The total count of bytes in all the filled buffers. */
98 private int filledBufferSum;
99
100 /** Flag to indicate if the buffers can be reused after reset */
101 private boolean reuseBuffers = true;
102
103 /**
104 * Constructs a new instance for subclasses.
105 */
106 public AbstractByteArrayOutputStream() {
107 // empty
108 }
109
110 /**
111 * Returns this instance typed to {@code T}.
112 *
113 * @return {@code this} instance
114 */
115 @SuppressWarnings("unchecked")
116 protected T asThis() {
117 return (T) this;
118 }
119
120 /**
121 * Does nothing.
122 *
123 * The methods in this class can be called after the stream has been closed without generating an {@link IOException}.
124 *
125 * @throws IOException never (this method should not declare this exception but it has to now due to backwards
126 * compatibility).
127 */
128 @Override
129 public void close() throws IOException {
130 //nop
131 }
132
133 /**
134 * Makes a new buffer available either by allocating
135 * a new one or re-cycling an existing one.
136 *
137 * @param newCount the size of the buffer if one is created.
138 */
139 protected void needNewBuffer(final int newCount) {
140 if (currentBufferIndex < buffers.size() - 1) {
141 // Recycling old buffer
142 filledBufferSum += currentBuffer.length;
143 currentBufferIndex++;
144 currentBuffer = buffers.get(currentBufferIndex);
145 } else {
146 // Creating new buffer
147 final int newBufferSize;
148 if (currentBuffer == null) {
149 // prevents 0 size buffers
150 newBufferSize = newCount > 0 ? newCount : DEFAULT_SIZE;
151 filledBufferSum = 0;
152 } else {
153 newBufferSize = Math.max(currentBuffer.length << 1, newCount - filledBufferSum);
154 filledBufferSum += currentBuffer.length;
155 }
156 currentBufferIndex++;
157 currentBuffer = IOUtils.byteArray(newBufferSize);
158 buffers.add(currentBuffer);
159 }
160 }
161
162 /**
163 * See {@link ByteArrayOutputStream#reset()}.
164 *
165 * @see ByteArrayOutputStream#reset()
166 */
167 public abstract void reset();
168
169 /**
170 * Implements a default reset behavior.
171 *
172 * @see ByteArrayOutputStream#reset()
173 */
174 protected void resetImpl() {
175 count = 0;
176 filledBufferSum = 0;
177 currentBufferIndex = 0;
178 if (reuseBuffers) {
179 currentBuffer = buffers.get(currentBufferIndex);
180 } else {
181 //Throw away old buffers
182 currentBuffer = null;
183 final int size = buffers.get(0).length;
184 buffers.clear();
185 needNewBuffer(size);
186 reuseBuffers = true;
187 }
188 }
189
190 /**
191 * Returns the current size of the byte array.
192 *
193 * @return the current size of the byte array.
194 */
195 public abstract int size();
196
197 /**
198 * Gets the current contents of this byte stream as a byte array.
199 * The result is independent of this stream.
200 *
201 * @return the current contents of this output stream, as a byte array.
202 * @see java.io.ByteArrayOutputStream#toByteArray()
203 */
204 public abstract byte[] toByteArray();
205
206 /**
207 * Gets the current contents of this byte stream as a byte array.
208 * The result is independent of this stream.
209 *
210 * @return the current contents of this output stream, as a byte array.
211 * @see java.io.ByteArrayOutputStream#toByteArray()
212 */
213 protected byte[] toByteArrayImpl() {
214 int remaining = count;
215 if (remaining == 0) {
216 return IOUtils.EMPTY_BYTE_ARRAY;
217 }
218 final byte[] newBuf = IOUtils.byteArray(remaining);
219 int pos = 0;
220 for (final byte[] buf : buffers) {
221 final int c = Math.min(buf.length, remaining);
222 System.arraycopy(buf, 0, newBuf, pos, c);
223 pos += c;
224 remaining -= c;
225 if (remaining == 0) {
226 break;
227 }
228 }
229 return newBuf;
230 }
231
232 /**
233 * Gets the current contents of this byte stream as an Input Stream. The
234 * returned stream is backed by buffers of {@code this} stream,
235 * avoiding memory allocation and copy, thus saving space and time.<br>
236 *
237 * @return the current contents of this output stream.
238 * @see java.io.ByteArrayOutputStream#toByteArray()
239 * @see #reset()
240 * @since 2.5
241 */
242 public abstract InputStream toInputStream();
243
244 /**
245 * Gets the current contents of this byte stream as an Input Stream. The
246 * returned stream is backed by buffers of {@code this} stream,
247 * avoiding memory allocation and copy, thus saving space and time.<br>
248 *
249 * @param <T> the type of the InputStream which makes up
250 * the {@link SequenceInputStream}.
251 * @param isConstructor A constructor for an InputStream which makes
252 * up the {@link SequenceInputStream}.
253 *
254 * @return the current contents of this output stream.
255 * @see java.io.ByteArrayOutputStream#toByteArray()
256 * @see #reset()
257 * @since 2.7
258 */
259 @SuppressWarnings("resource") // The result InputStream MUST be managed by the call site.
260 protected <T extends InputStream> InputStream toInputStream(final InputStreamConstructor<T> isConstructor) {
261 int remaining = count;
262 if (remaining == 0) {
263 return ClosedInputStream.INSTANCE;
264 }
265 final List<T> list = new ArrayList<>(buffers.size());
266 for (final byte[] buf : buffers) {
267 final int c = Math.min(buf.length, remaining);
268 list.add(isConstructor.construct(buf, 0, c));
269 remaining -= c;
270 if (remaining == 0) {
271 break;
272 }
273 }
274 reuseBuffers = false;
275 return new SequenceInputStream(Collections.enumeration(list));
276 }
277
278 /**
279 * Gets the current contents of this byte stream as a string using the virtual machine's {@linkplain Charset#defaultCharset() default charset}.
280 *
281 * @return the contents of the byte array as a String.
282 * @see java.io.ByteArrayOutputStream#toString()
283 * @see Charset#defaultCharset()
284 * @deprecated Use {@link #toString(String)} instead.
285 */
286 @Override
287 @Deprecated
288 public String toString() {
289 // make explicit the use of the default charset
290 return new String(toByteArray(), Charset.defaultCharset());
291 }
292
293 /**
294 * Gets the current contents of this byte stream as a string
295 * using the specified encoding.
296 *
297 * @param charset the character encoding.
298 * @return the string converted from the byte array.
299 * @see java.io.ByteArrayOutputStream#toString(String)
300 * @since 2.5
301 */
302 public String toString(final Charset charset) {
303 return new String(toByteArray(), charset);
304 }
305
306 /**
307 * Gets the current contents of this byte stream as a string
308 * using the specified encoding.
309 *
310 * @param enc the name of the character encoding.
311 * @return the string converted from the byte array.
312 * @throws UnsupportedEncodingException if the encoding is not supported.
313 * @see java.io.ByteArrayOutputStream#toString(String)
314 */
315 public String toString(final String enc) throws UnsupportedEncodingException {
316 return new String(toByteArray(), enc);
317 }
318
319 /**
320 * Writes {@code b.length} bytes from the given byte array to this output stream. This has same effect as {@code write(b, 0, b.length)}.
321 *
322 * @param b the data.
323 * @see #write(byte[], int, int)
324 * @since 2.19.0
325 */
326 @Override
327 public void write(final byte b[]) {
328 write(b, 0, b.length);
329 }
330
331 @Override
332 public abstract void write(byte[] b, int off, int len);
333
334 /**
335 * Writes the bytes for given CharSequence encoded using a Charset.
336 *
337 * @param data The String to convert to bytes. not null.
338 * @param charset The {@link Charset} o encode the {@code String}, null means the default encoding.
339 * @return {@code this} instance.
340 * @since 2.19.0
341 */
342 public T write(final CharSequence data, final Charset charset) {
343 write(data.toString().getBytes(Charsets.toCharset(charset)));
344 return asThis();
345 }
346
347 /**
348 * Writes the entire contents of the specified input stream to this byte stream. Bytes from the input stream are read directly into the internal buffer of
349 * this stream.
350 *
351 * @param in the input stream to read from.
352 * @return total number of bytes read from the input stream (and written to this stream)
353 * @throws IOException if an I/O error occurs while reading the input stream.
354 * @since 1.4
355 */
356 public abstract int write(InputStream in) throws IOException;
357
358 @Override
359 public abstract void write(int b);
360
361 /**
362 * Writes the bytes to the byte array.
363 *
364 * @param b the bytes to write.
365 * @param off The start offset.
366 * @param len The number of bytes to write.
367 */
368 protected void writeImpl(final byte[] b, final int off, final int len) {
369 final int newCount = count + len;
370 int remaining = len;
371 int inBufferPos = count - filledBufferSum;
372 while (remaining > 0) {
373 final int part = Math.min(remaining, currentBuffer.length - inBufferPos);
374 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
375 remaining -= part;
376 if (remaining > 0) {
377 needNewBuffer(newCount);
378 inBufferPos = 0;
379 }
380 }
381 count = newCount;
382 }
383
384 /**
385 * Writes the entire contents of the specified input stream to this byte stream. Bytes from the input stream are read directly into the internal buffer of
386 * this stream.
387 *
388 * @param in the input stream to read from.
389 * @return total number of bytes read from the input stream (and written to this stream).
390 * @throws IOException if an I/O error occurs while reading the input stream.
391 * @since 2.7
392 */
393 protected int writeImpl(final InputStream in) throws IOException {
394 int readCount = 0;
395 int inBufferPos = count - filledBufferSum;
396 int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
397 while (n != EOF) {
398 readCount += n;
399 inBufferPos += n;
400 count += n;
401 if (inBufferPos == currentBuffer.length) {
402 needNewBuffer(currentBuffer.length);
403 inBufferPos = 0;
404 }
405 n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
406 }
407 return readCount;
408 }
409
410 /**
411 * Writes a byte to byte array.
412 *
413 * @param b the byte to write.
414 */
415 protected void writeImpl(final int b) {
416 int inBufferPos = count - filledBufferSum;
417 if (inBufferPos == currentBuffer.length) {
418 needNewBuffer(count + 1);
419 inBufferPos = 0;
420 }
421 currentBuffer[inBufferPos] = (byte) b;
422 count++;
423 }
424
425 /**
426 * Writes the entire contents of this byte stream to the specified output stream.
427 *
428 * @param out the output stream to write to.
429 * @throws IOException if an I/O error occurs, such as if the stream is closed.
430 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
431 */
432 public abstract void writeTo(OutputStream out) throws IOException;
433
434 /**
435 * Writes the entire contents of this byte stream to the specified output stream.
436 *
437 * @param out the output stream to write to.
438 * @throws IOException if an I/O error occurs, such as if the stream is closed.
439 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
440 */
441 protected void writeToImpl(final OutputStream out) throws IOException {
442 int remaining = count;
443 for (final byte[] buf : buffers) {
444 final int c = Math.min(buf.length, remaining);
445 out.write(buf, 0, c);
446 remaining -= c;
447 if (remaining == 0) {
448 break;
449 }
450 }
451 }
452
453 }