diff options
Diffstat (limited to 'src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java')
-rw-r--r-- | src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java b/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java new file mode 100644 index 00000000..dd8b0e42 --- /dev/null +++ b/src/main/java/org/apache/commons/io/output/AbstractByteArrayOutputStream.java @@ -0,0 +1,411 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.io.output; + +import static org.apache.commons.io.IOUtils.EOF; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ClosedInputStream; + +/** + * This is the base class for implementing an output stream in which the data + * is written into a byte array. The buffer automatically grows as data + * is written to it. + * <p> + * The data can be retrieved using {@code toByteArray()} and + * {@code toString()}. + * Closing an {@link AbstractByteArrayOutputStream} has no effect. The methods in + * this class can be called after the stream has been closed without + * generating an {@link IOException}. + * </p> + * <p> + * This is the base for an alternative implementation of the + * {@link java.io.ByteArrayOutputStream} class. The original implementation + * only allocates 32 bytes at the beginning. As this class is designed for + * heavy duty it starts at {@value #DEFAULT_SIZE} bytes. In contrast to the original it doesn't + * reallocate the whole memory block but allocates additional buffers. This + * way no buffers need to be garbage collected and the contents don't have + * to be copied to the new buffer. This class is designed to behave exactly + * like the original. The only exception is the deprecated + * {@link java.io.ByteArrayOutputStream#toString(int)} method that has been + * ignored. + * </p> + * + * @since 2.7 + */ +public abstract class AbstractByteArrayOutputStream extends OutputStream { + + /** + * Constructor for an InputStream subclass. + * + * @param <T> the type of the InputStream. + */ + @FunctionalInterface + protected interface InputStreamConstructor<T extends InputStream> { + + /** + * Constructs an InputStream subclass. + * + * @param buf the buffer + * @param offset the offset into the buffer + * @param length the length of the buffer + * + * @return the InputStream subclass. + */ + T construct(final byte[] buf, final int offset, final int length); + } + + static final int DEFAULT_SIZE = 1024; + + /** The list of buffers, which grows and never reduces. */ + private final List<byte[]> buffers = new ArrayList<>(); + + /** The index of the current buffer. */ + private int currentBufferIndex; + + /** The total count of bytes in all the filled buffers. */ + private int filledBufferSum; + + /** The current buffer. */ + private byte[] currentBuffer; + + /** The total count of bytes written. */ + protected int count; + + /** Flag to indicate if the buffers can be reused after reset */ + private boolean reuseBuffers = true; + + /** + * Does nothing. + * + * The methods in this class can be called after the stream has been closed without generating an {@link IOException}. + * + * @throws IOException never (this method should not declare this exception but it has to now due to backwards + * compatibility) + */ + @Override + public void close() throws IOException { + //nop + } + + /** + * Makes a new buffer available either by allocating + * a new one or re-cycling an existing one. + * + * @param newCount the size of the buffer if one is created + */ + protected void needNewBuffer(final int newCount) { + if (currentBufferIndex < buffers.size() - 1) { + // Recycling old buffer + filledBufferSum += currentBuffer.length; + + currentBufferIndex++; + currentBuffer = buffers.get(currentBufferIndex); + } else { + // Creating new buffer + final int newBufferSize; + if (currentBuffer == null) { + newBufferSize = newCount; + filledBufferSum = 0; + } else { + newBufferSize = Math.max(currentBuffer.length << 1, newCount - filledBufferSum); + filledBufferSum += currentBuffer.length; + } + + currentBufferIndex++; + currentBuffer = IOUtils.byteArray(newBufferSize); + buffers.add(currentBuffer); + } + } + + /** + * @see java.io.ByteArrayOutputStream#reset() + */ + public abstract void reset(); + + /** + * @see java.io.ByteArrayOutputStream#reset() + */ + protected void resetImpl() { + count = 0; + filledBufferSum = 0; + currentBufferIndex = 0; + if (reuseBuffers) { + currentBuffer = buffers.get(currentBufferIndex); + } else { + //Throw away old buffers + currentBuffer = null; + final int size = buffers.get(0).length; + buffers.clear(); + needNewBuffer(size); + reuseBuffers = true; + } + } + + /** + * Returns the current size of the byte array. + * + * @return the current size of the byte array + */ + public abstract int size(); + + /** + * Gets the current contents of this byte stream as a byte array. + * The result is independent of this stream. + * + * @return the current contents of this output stream, as a byte array + * @see java.io.ByteArrayOutputStream#toByteArray() + */ + public abstract byte[] toByteArray(); + + /** + * Gets the current contents of this byte stream as a byte array. + * The result is independent of this stream. + * + * @return the current contents of this output stream, as a byte array + * @see java.io.ByteArrayOutputStream#toByteArray() + */ + protected byte[] toByteArrayImpl() { + int remaining = count; + if (remaining == 0) { + return IOUtils.EMPTY_BYTE_ARRAY; + } + final byte[] newBuf = IOUtils.byteArray(remaining); + int pos = 0; + for (final byte[] buf : buffers) { + final int c = Math.min(buf.length, remaining); + System.arraycopy(buf, 0, newBuf, pos, c); + pos += c; + remaining -= c; + if (remaining == 0) { + break; + } + } + return newBuf; + } + + /** + * Gets the current contents of this byte stream as an Input Stream. The + * returned stream is backed by buffers of {@code this} stream, + * avoiding memory allocation and copy, thus saving space and time.<br> + * + * @return the current contents of this output stream. + * @see java.io.ByteArrayOutputStream#toByteArray() + * @see #reset() + * @since 2.5 + */ + public abstract InputStream toInputStream(); + + /** + * Gets the current contents of this byte stream as an Input Stream. The + * returned stream is backed by buffers of {@code this} stream, + * avoiding memory allocation and copy, thus saving space and time.<br> + * + * @param <T> the type of the InputStream which makes up + * the {@link SequenceInputStream}. + * @param isConstructor A constructor for an InputStream which makes + * up the {@link SequenceInputStream}. + * + * @return the current contents of this output stream. + * @see java.io.ByteArrayOutputStream#toByteArray() + * @see #reset() + * @since 2.7 + */ + @SuppressWarnings("resource") // The result InputStream MUST be managed by the call site. + protected <T extends InputStream> InputStream toInputStream( + final InputStreamConstructor<T> isConstructor) { + int remaining = count; + if (remaining == 0) { + return ClosedInputStream.INSTANCE; + } + final List<T> list = new ArrayList<>(buffers.size()); + for (final byte[] buf : buffers) { + final int c = Math.min(buf.length, remaining); + list.add(isConstructor.construct(buf, 0, c)); + remaining -= c; + if (remaining == 0) { + break; + } + } + reuseBuffers = false; + return new SequenceInputStream(Collections.enumeration(list)); + } + + /** + * Gets the current contents of this byte stream as a string + * using the platform default charset. + * @return the contents of the byte array as a String + * @see java.io.ByteArrayOutputStream#toString() + * @deprecated 2.5 use {@link #toString(String)} instead + */ + @Override + @Deprecated + public String toString() { + // make explicit the use of the default charset + return new String(toByteArray(), Charset.defaultCharset()); + } + + /** + * Gets the current contents of this byte stream as a string + * using the specified encoding. + * + * @param charset the character encoding + * @return the string converted from the byte array + * @see java.io.ByteArrayOutputStream#toString(String) + * @since 2.5 + */ + public String toString(final Charset charset) { + return new String(toByteArray(), charset); + } + + /** + * Gets the current contents of this byte stream as a string + * using the specified encoding. + * + * @param enc the name of the character encoding + * @return the string converted from the byte array + * @throws UnsupportedEncodingException if the encoding is not supported + * @see java.io.ByteArrayOutputStream#toString(String) + */ + public String toString(final String enc) throws UnsupportedEncodingException { + return new String(toByteArray(), enc); + } + + @Override + public abstract void write(final byte[] b, final int off, final int len); + + /** + * 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 this stream. + * + * @param in the input stream to read from + * @return total number of bytes read from the input stream + * (and written to this stream) + * @throws IOException if an I/O error occurs while reading the input stream + * @since 1.4 + */ + public abstract int write(final InputStream in) throws IOException; + + @Override + public abstract void write(final int b); + + /** + * Writes the bytes to the byte array. + * @param b the bytes to write + * @param off The start offset + * @param len The number of bytes to write + */ + protected void writeImpl(final byte[] b, final int off, final int len) { + final int newCount = count + len; + int remaining = len; + int inBufferPos = count - filledBufferSum; + while (remaining > 0) { + final int part = Math.min(remaining, currentBuffer.length - inBufferPos); + System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); + remaining -= part; + if (remaining > 0) { + needNewBuffer(newCount); + inBufferPos = 0; + } + } + count = newCount; + } + + /** + * 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 this stream. + * + * @param in the input stream to read from + * @return total number of bytes read from the input stream + * (and written to this stream) + * @throws IOException if an I/O error occurs while reading the input stream + * @since 2.7 + */ + protected int writeImpl(final InputStream in) throws IOException { + int readCount = 0; + int inBufferPos = count - filledBufferSum; + int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); + while (n != EOF) { + readCount += n; + inBufferPos += n; + count += n; + if (inBufferPos == currentBuffer.length) { + needNewBuffer(currentBuffer.length); + inBufferPos = 0; + } + n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); + } + return readCount; + } + + /** + * Write a byte to byte array. + * @param b the byte to write + */ + protected void writeImpl(final int b) { + int inBufferPos = count - filledBufferSum; + if (inBufferPos == currentBuffer.length) { + needNewBuffer(count + 1); + inBufferPos = 0; + } + currentBuffer[inBufferPos] = (byte) b; + count++; + } + + /** + * Writes the entire contents of this byte stream to the + * specified output stream. + * + * @param out the output stream to write to + * @throws IOException if an I/O error occurs, such as if the stream is closed + * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) + */ + public abstract void writeTo(final OutputStream out) throws IOException; + + /** + * Writes the entire contents of this byte stream to the + * specified output stream. + * + * @param out the output stream to write to + * @throws IOException if an I/O error occurs, such as if the stream is closed + * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) + */ + protected void writeToImpl(final OutputStream out) throws IOException { + int remaining = count; + for (final byte[] buf : buffers) { + final int c = Math.min(buf.length, remaining); + out.write(buf, 0, c); + remaining -= c; + if (remaining == 0) { + break; + } + } + } + +} |