001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.io.output;
018:
019: import java.io.IOException;
020: import java.io.OutputStream;
021: import java.io.UnsupportedEncodingException;
022: import java.util.ArrayList;
023: import java.util.List;
024:
025: /**
026: * This class implements an output stream in which the data is
027: * written into a byte array. The buffer automatically grows as data
028: * is written to it.
029: * <p>
030: * The data can be retrieved using <code>toByteArray()</code> and
031: * <code>toString()</code>.
032: * <p>
033: * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
034: * this class can be called after the stream has been closed without
035: * generating an <tt>IOException</tt>.
036: * <p>
037: * This is an alternative implementation of the java.io.ByteArrayOutputStream
038: * class. The original implementation only allocates 32 bytes at the beginning.
039: * As this class is designed for heavy duty it starts at 1024 bytes. In contrast
040: * to the original it doesn't reallocate the whole memory block but allocates
041: * additional buffers. This way no buffers need to be garbage collected and
042: * the contents don't have to be copied to the new buffer. This class is
043: * designed to behave exactly like the original. The only exception is the
044: * deprecated toString(int) method that has been ignored.
045: *
046: * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
047: * @author Holger Hoffstatte
048: * @version $Id: ByteArrayOutputStream.java 491007 2006-12-29 13:50:34Z scolebourne $
049: */
050: public class ByteArrayOutputStream extends OutputStream {
051:
052: /** A singleton empty byte array. */
053: private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
054:
055: /** The list of buffers, which grows and never reduces. */
056: private List buffers = new ArrayList();
057: /** The index of the current buffer. */
058: private int currentBufferIndex;
059: /** The total count of bytes in all the filled buffers. */
060: private int filledBufferSum;
061: /** The current buffer. */
062: private byte[] currentBuffer;
063: /** The total count of bytes written. */
064: private int count;
065:
066: /**
067: * Creates a new byte array output stream. The buffer capacity is
068: * initially 1024 bytes, though its size increases if necessary.
069: */
070: public ByteArrayOutputStream() {
071: this (1024);
072: }
073:
074: /**
075: * Creates a new byte array output stream, with a buffer capacity of
076: * the specified size, in bytes.
077: *
078: * @param size the initial size
079: * @throws IllegalArgumentException if size is negative
080: */
081: public ByteArrayOutputStream(int size) {
082: if (size < 0) {
083: throw new IllegalArgumentException(
084: "Negative initial size: " + size);
085: }
086: needNewBuffer(size);
087: }
088:
089: /**
090: * Return the appropriate <code>byte[]</code> buffer
091: * specified by index.
092: *
093: * @param index the index of the buffer required
094: * @return the buffer
095: */
096: private byte[] getBuffer(int index) {
097: return (byte[]) buffers.get(index);
098: }
099:
100: /**
101: * Makes a new buffer available either by allocating
102: * a new one or re-cycling an existing one.
103: *
104: * @param newcount the size of the buffer if one is created
105: */
106: private void needNewBuffer(int newcount) {
107: if (currentBufferIndex < buffers.size() - 1) {
108: //Recycling old buffer
109: filledBufferSum += currentBuffer.length;
110:
111: currentBufferIndex++;
112: currentBuffer = getBuffer(currentBufferIndex);
113: } else {
114: //Creating new buffer
115: int newBufferSize;
116: if (currentBuffer == null) {
117: newBufferSize = newcount;
118: filledBufferSum = 0;
119: } else {
120: newBufferSize = Math.max(currentBuffer.length << 1,
121: newcount - filledBufferSum);
122: filledBufferSum += currentBuffer.length;
123: }
124:
125: currentBufferIndex++;
126: currentBuffer = new byte[newBufferSize];
127: buffers.add(currentBuffer);
128: }
129: }
130:
131: /**
132: * @see java.io.OutputStream#write(byte[], int, int)
133: */
134: public void write(byte[] b, int off, int len) {
135: if ((off < 0) || (off > b.length) || (len < 0)
136: || ((off + len) > b.length) || ((off + len) < 0)) {
137: throw new IndexOutOfBoundsException();
138: } else if (len == 0) {
139: return;
140: }
141: synchronized (this ) {
142: int newcount = count + len;
143: int remaining = len;
144: int inBufferPos = count - filledBufferSum;
145: while (remaining > 0) {
146: int part = Math.min(remaining, currentBuffer.length
147: - inBufferPos);
148: System.arraycopy(b, off + len - remaining,
149: currentBuffer, inBufferPos, part);
150: remaining -= part;
151: if (remaining > 0) {
152: needNewBuffer(newcount);
153: inBufferPos = 0;
154: }
155: }
156: count = newcount;
157: }
158: }
159:
160: /**
161: * @see java.io.OutputStream#write(int)
162: */
163: public synchronized void write(int b) {
164: int inBufferPos = count - filledBufferSum;
165: if (inBufferPos == currentBuffer.length) {
166: needNewBuffer(count + 1);
167: inBufferPos = 0;
168: }
169: currentBuffer[inBufferPos] = (byte) b;
170: count++;
171: }
172:
173: /**
174: * @see java.io.ByteArrayOutputStream#size()
175: */
176: public synchronized int size() {
177: return count;
178: }
179:
180: /**
181: * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
182: * this class can be called after the stream has been closed without
183: * generating an <tt>IOException</tt>.
184: *
185: * @throws IOException never (this method should not declare this exception
186: * but it has to now due to backwards compatability)
187: */
188: public void close() throws IOException {
189: //nop
190: }
191:
192: /**
193: * @see java.io.ByteArrayOutputStream#reset()
194: */
195: public synchronized void reset() {
196: count = 0;
197: filledBufferSum = 0;
198: currentBufferIndex = 0;
199: currentBuffer = getBuffer(currentBufferIndex);
200: }
201:
202: /**
203: * Writes the entire contents of this byte stream to the
204: * specified output stream.
205: *
206: * @param out the output stream to write to
207: * @throws IOException if an I/O error occurs, such as if the stream is closed
208: * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
209: */
210: public synchronized void writeTo(OutputStream out)
211: throws IOException {
212: int remaining = count;
213: for (int i = 0; i < buffers.size(); i++) {
214: byte[] buf = getBuffer(i);
215: int c = Math.min(buf.length, remaining);
216: out.write(buf, 0, c);
217: remaining -= c;
218: if (remaining == 0) {
219: break;
220: }
221: }
222: }
223:
224: /**
225: * Gets the curent contents of this byte stream as a byte array.
226: * The result is independent of this stream.
227: *
228: * @return the current contents of this output stream, as a byte array
229: * @see java.io.ByteArrayOutputStream#toByteArray()
230: */
231: public synchronized byte[] toByteArray() {
232: int remaining = count;
233: if (remaining == 0) {
234: return EMPTY_BYTE_ARRAY;
235: }
236: byte newbuf[] = new byte[remaining];
237: int pos = 0;
238: for (int i = 0; i < buffers.size(); i++) {
239: byte[] buf = getBuffer(i);
240: int c = Math.min(buf.length, remaining);
241: System.arraycopy(buf, 0, newbuf, pos, c);
242: pos += c;
243: remaining -= c;
244: if (remaining == 0) {
245: break;
246: }
247: }
248: return newbuf;
249: }
250:
251: /**
252: * Gets the curent contents of this byte stream as a string.
253: *
254: * @see java.io.ByteArrayOutputStream#toString()
255: */
256: public String toString() {
257: return new String(toByteArray());
258: }
259:
260: /**
261: * Gets the curent contents of this byte stream as a string
262: * using the specified encoding.
263: *
264: * @param enc the name of the character encoding
265: * @return the string converted from the byte array
266: * @throws UnsupportedEncodingException if the encoding is not supported
267: * @see java.io.ByteArrayOutputStream#toString(String)
268: */
269: public String toString(String enc)
270: throws UnsupportedEncodingException {
271: return new String(toByteArray(), enc);
272: }
273:
274: }
|