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 */ 017package org.apache.wicket.util.io; 018 019import java.io.IOException; 020import java.io.OutputStream; 021import java.io.UnsupportedEncodingException; 022import java.util.List; 023 024/** 025 * This class implements an output stream in which the data is written into a byte array. The buffer 026 * automatically grows as data is written to it. 027 * <p> 028 * The data can be retrieved using <code>toByteArray()</code> and <code>toString()</code>. 029 * <p> 030 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in this class can be called 031 * after the stream has been closed without generating an <tt>IOException</tt>. 032 * <p> 033 * This is an alternative implementation of the java.io.ByteArrayOutputStream class. The original 034 * implementation only allocates 32 bytes at the beginning. As this class is designed for heavy duty 035 * it starts at 1024 bytes. In contrast to the original it doesn't reallocate the whole memory block 036 * but allocates additional buffers. This way no buffers need to be garbage collected and the 037 * contents don't have to be copied to the new buffer. This class is designed to behave exactly like 038 * the original. The only exception is the deprecated toString(int) method that has been ignored. 039 * 040 * @author <a href="mailto:[email protected]">Jeremias Maerki</a> 041 * @version $Id$ 042 */ 043public class ByteArrayOutputStream extends OutputStream 044{ 045 private final List<byte[]> 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 bytes, though 053 * its size increases if necessary. 054 */ 055 public ByteArrayOutputStream() 056 { 057 this(1024); 058 } 059 060 /** 061 * Creates a new byte array output stream, with a buffer capacity of the specified size, in 062 * bytes. 063 * 064 * @param size 065 * the initial size. 066 * @exception IllegalArgumentException 067 * if size is negative. 068 */ 069 public ByteArrayOutputStream(final int size) 070 { 071 if (size < 0) 072 { 073 throw new IllegalArgumentException("Negative initial size: " + size); 074 } 075 needNewBuffer(size); 076 } 077 078 /** 079 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in this class can be 080 * called after the stream has been closed without generating an <tt>IOException</tt>. 081 * 082 * @throws IOException 083 * in case an I/O error occurs 084 */ 085 @Override 086 public void close() throws IOException 087 { 088 // nop 089 } 090 091 /** 092 * @see java.io.ByteArrayOutputStream#reset() 093 */ 094 public synchronized void reset() 095 { 096 count = 0; 097 filledBufferSum = 0; 098 currentBufferIndex = 0; 099 currentBuffer = getBuffer(currentBufferIndex); 100 } 101 102 /** 103 * Gets the size. 104 * 105 * @return the size 106 */ 107 public int size() 108 { 109 return count; 110 } 111 112 /** 113 * Writes to a byte array. 114 * 115 * @return this is a byte array 116 */ 117 public synchronized byte[] toByteArray() 118 { 119 int remaining = count; 120 int pos = 0; 121 byte newbuf[] = new byte[count]; 122 for (int i = 0; i < buffers.size(); i++) 123 { 124 byte[] buf = getBuffer(i); 125 int c = Math.min(buf.length, remaining); 126 System.arraycopy(buf, 0, newbuf, pos, c); 127 pos += c; 128 remaining -= c; 129 if (remaining == 0) 130 { 131 break; 132 } 133 } 134 return newbuf; 135 } 136 137 /** 138 * @see java.lang.Object#toString() 139 */ 140 @Override 141 public String toString() 142 { 143 return new String(toByteArray()); 144 } 145 146 /** 147 * This as a string using the provided encoding. 148 * 149 * @param enc 150 * the encoding to use 151 * @return This as a string using the provided encoding 152 * @throws UnsupportedEncodingException 153 */ 154 public String toString(final String enc) throws UnsupportedEncodingException 155 { 156 return new String(toByteArray(), enc); 157 } 158 159 /** 160 * @see java.io.OutputStream#write(byte[], int, int) 161 */ 162 @Override 163 public synchronized void write(final byte[] b, final int off, final int len) 164 { 165 if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || 166 ((off + len) < 0)) 167 { 168 throw new IndexOutOfBoundsException(); 169 } 170 else if (len == 0) 171 { 172 return; 173 } 174 int newcount = count + len; 175 int remaining = len; 176 int inBufferPos = count - filledBufferSum; 177 while (remaining > 0) 178 { 179 int part = Math.min(remaining, currentBuffer.length - inBufferPos); 180 System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); 181 remaining -= part; 182 if (remaining > 0) 183 { 184 needNewBuffer(newcount); 185 inBufferPos = 0; 186 } 187 } 188 count = newcount; 189 } 190 191 /** 192 * Calls the write(byte[]) method. 193 * 194 * @see java.io.OutputStream#write(int) 195 */ 196 @Override 197 public synchronized void write(final int b) 198 { 199 write(new byte[] { (byte)b }, 0, 1); 200 } 201 202 /** 203 * Write to the given output stream. 204 * 205 * @param out 206 * the output stream to write to 207 * @throws IOException 208 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream) 209 */ 210 public synchronized void writeTo(final OutputStream out) throws IOException 211 { 212 int remaining = count; 213 for (int i = 0; i < buffers.size(); i++) 214 { 215 byte[] buf = getBuffer(i); 216 int c = Math.min(buf.length, remaining); 217 out.write(buf, 0, c); 218 remaining -= c; 219 if (remaining == 0) 220 { 221 break; 222 } 223 } 224 } 225 226 private byte[] getBuffer(final int index) 227 { 228 return buffers.get(index); 229 } 230 231 private void needNewBuffer(final int newcount) 232 { 233 if (currentBufferIndex < buffers.size() - 1) 234 { 235 // Recycling old buffer 236 filledBufferSum += currentBuffer.length; 237 238 currentBufferIndex++; 239 currentBuffer = getBuffer(currentBufferIndex); 240 } 241 else 242 { 243 // Creating new buffer 244 int newBufferSize; 245 if (currentBuffer == null) 246 { 247 newBufferSize = newcount; 248 filledBufferSum = 0; 249 } 250 else 251 { 252 newBufferSize = Math.max(currentBuffer.length << 1, newcount - filledBufferSum); 253 filledBufferSum += currentBuffer.length; 254 } 255 256 currentBufferIndex++; 257 currentBuffer = new byte[newBufferSize]; 258 buffers.add(currentBuffer); 259 } 260 } 261 262}