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