001: /*
002: * Copyright 2003,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package wicket.util.io;
018:
019: import java.io.IOException;
020: import java.io.OutputStream;
021: import java.io.UnsupportedEncodingException;
022: import java.util.List;
023:
024: /**
025: * This class implements an output stream in which the data is written into a byte array.
026: * The buffer automatically grows as data is written to it.
027: * <p>
028: * The data can be retrieved using <code>toByteArray()</code> and
029: * <code>toString()</code>.
030: * <p>
031: * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in this class can
032: * be called after the stream has been closed without generating an <tt>IOException</tt>.
033: * <p>
034: * This is an alternative implementation of the java.io.ByteArrayOutputStream class. The
035: * original implementation only allocates 32 bytes at the beginning. As this class is
036: * designed for heavy duty it starts at 1024 bytes. In contrast to the original it doesn't
037: * reallocate the whole memory block but allocates additional buffers. This way no buffers
038: * need to be garbage collected and the contents don't have to be copied to the new
039: * buffer. This class is designed to behave exactly like the original. The only exception
040: * is the deprecated toString(int) method that has been ignored.
041: * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
042: * @version $Id$
043: */
044: public class ByteArrayOutputStream extends OutputStream {
045: private List buffers = new java.util.ArrayList();
046: private int count;
047: private byte[] currentBuffer;
048: private int currentBufferIndex;
049: private int filledBufferSum;
050:
051: /**
052: * Creates a new byte array output stream. The buffer capacity is initially 1024
053: * bytes, though its size increases if necessary.
054: */
055: public ByteArrayOutputStream() {
056: this (1024);
057: }
058:
059: /**
060: * Creates a new byte array output stream, with a buffer capacity of the specified
061: * size, in bytes.
062: * @param size the initial size.
063: * @exception IllegalArgumentException if size is negative.
064: */
065: public ByteArrayOutputStream(int size) {
066: if (size < 0) {
067: throw new IllegalArgumentException(
068: "Negative initial size: " + size);
069: }
070: needNewBuffer(size);
071: }
072:
073: /**
074: * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in this class
075: * can be called after the stream has been closed without generating an
076: * <tt>IOException</tt>.
077: * @throws IOException in case an I/O error occurs
078: */
079: public void close() throws IOException {
080: // nop
081: }
082:
083: /**
084: * @see java.io.ByteArrayOutputStream#reset()
085: */
086: public synchronized void reset() {
087: count = 0;
088: filledBufferSum = 0;
089: currentBufferIndex = 0;
090: currentBuffer = getBuffer(currentBufferIndex);
091: }
092:
093: /**
094: * Gets the size.
095: * @return the size
096: */
097: public int size() {
098: return count;
099: }
100:
101: /**
102: * Writes to a byte array.
103: * @return this is a byte array
104: */
105: public synchronized byte[] toByteArray() {
106: int remaining = count;
107: int pos = 0;
108: byte newbuf[] = new byte[count];
109: for (int i = 0; i < buffers.size(); i++) {
110: byte[] buf = getBuffer(i);
111: int c = Math.min(buf.length, remaining);
112: System.arraycopy(buf, 0, newbuf, pos, c);
113: pos += c;
114: remaining -= c;
115: if (remaining == 0) {
116: break;
117: }
118: }
119: return newbuf;
120: }
121:
122: /**
123: * @see java.lang.Object#toString()
124: */
125: public String toString() {
126: return new String(toByteArray());
127: }
128:
129: /**
130: * This as a string using the provided encoding.
131: * @param enc the encoding to use
132: * @return This as a string using the provided encoding
133: * @throws UnsupportedEncodingException
134: */
135: public String toString(String enc)
136: throws UnsupportedEncodingException {
137: return new String(toByteArray(), enc);
138: }
139:
140: /**
141: * @see java.io.OutputStream#write(byte[], int, int)
142: */
143: public synchronized void write(byte[] b, int off, int len) {
144: if ((off < 0) || (off > b.length) || (len < 0)
145: || ((off + len) > b.length) || ((off + len) < 0)) {
146: throw new IndexOutOfBoundsException();
147: } else if (len == 0) {
148: return;
149: }
150: int newcount = count + len;
151: int remaining = len;
152: int inBufferPos = count - filledBufferSum;
153: while (remaining > 0) {
154: int part = Math.min(remaining, currentBuffer.length
155: - inBufferPos);
156: System.arraycopy(b, off + len - remaining, currentBuffer,
157: inBufferPos, part);
158: remaining -= part;
159: if (remaining > 0) {
160: needNewBuffer(newcount);
161: inBufferPos = 0;
162: }
163: }
164: count = newcount;
165: }
166:
167: /**
168: * Calls the write(byte[]) method.
169: * @see java.io.OutputStream#write(int)
170: */
171: public synchronized void write(int b) {
172: write(new byte[] { (byte) b }, 0, 1);
173: }
174:
175: /**
176: * Write to the given output stream.
177: * @param out the output stream to write to
178: * @throws IOException
179: * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
180: */
181: public synchronized void writeTo(OutputStream out)
182: throws IOException {
183: int remaining = count;
184: for (int i = 0; i < buffers.size(); i++) {
185: byte[] buf = getBuffer(i);
186: int c = Math.min(buf.length, remaining);
187: out.write(buf, 0, c);
188: remaining -= c;
189: if (remaining == 0) {
190: break;
191: }
192: }
193: }
194:
195: private byte[] getBuffer(int index) {
196: return (byte[]) buffers.get(index);
197: }
198:
199: private void needNewBuffer(int newcount) {
200: if (currentBufferIndex < buffers.size() - 1) {
201: // Recycling old buffer
202: filledBufferSum += currentBuffer.length;
203:
204: currentBufferIndex++;
205: currentBuffer = getBuffer(currentBufferIndex);
206: } else {
207: // Creating new buffer
208: int newBufferSize;
209: if (currentBuffer == null) {
210: newBufferSize = newcount;
211: filledBufferSum = 0;
212: } else {
213: newBufferSize = Math.max(currentBuffer.length << 1,
214: newcount - filledBufferSum);
215: filledBufferSum += currentBuffer.length;
216: }
217:
218: currentBufferIndex++;
219: currentBuffer = new byte[newBufferSize];
220: buffers.add(currentBuffer);
221: }
222: }
223:
224: }
|