1 /* 2 * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved. 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2.1 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public 15 * License along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 * 18 * Additionally licensed with: 19 * 20 * Licensed under the Apache License, Version 2.0 (the "License"); 21 * you may not use this file except in compliance with the License. 22 * You may obtain a copy of the License at 23 * 24 * http://www.apache.org/licenses/LICENSE-2.0 25 * 26 * Unless required by applicable law or agreed to in writing, software 27 * distributed under the License is distributed on an "AS IS" BASIS, 28 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 * See the License for the specific language governing permissions and 30 * limitations under the License. 31 */ 32 package org.spf4j.io; 33 34 import edu.umd.cs.findbugs.annotations.CleanupObligation; 35 import edu.umd.cs.findbugs.annotations.DischargesObligation; 36 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.io.OutputStream; 40 import java.nio.charset.Charset; 41 import java.nio.charset.StandardCharsets; 42 import java.util.Arrays; 43 import javax.annotation.Nullable; 44 import org.spf4j.recyclable.SizedRecyclingSupplier; 45 import org.spf4j.recyclable.impl.ArraySuppliers; 46 47 /** 48 * Utility class to avoid replicating byte arrays for no good reason. 49 * 50 * @author zoly 51 */ 52 @SuppressFBWarnings("EI_EXPOSE_REP") 53 @CleanupObligation 54 public final class ByteArrayBuilder extends OutputStream { 55 56 /** 57 * The maximum size of array to allocate. Some VMs reserve some header words in an array. Attempts to allocate larger 58 * arrays may result in OutOfMemoryError: Requested array size exceeds VM limit 59 */ 60 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 61 62 /** 63 * The buffer where data is stored. 64 */ 65 private byte[] buf; 66 67 /** 68 * The number of valid bytes in the buffer. 69 */ 70 private int count; 71 72 @Nullable 73 private final SizedRecyclingSupplier<byte[]> arraySupplier; 74 75 public ByteArrayBuilder() { 76 this(256, ArraySuppliers.Bytes.TL_SUPPLIER); 77 } 78 79 public ByteArrayBuilder(final int size) { 80 this(size, ArraySuppliers.Bytes.TL_SUPPLIER); 81 } 82 83 public synchronized byte[] getBuffer() { 84 return buf; 85 } 86 87 /** 88 * Creates a new byte array output stream, with a buffer capacity of the specified size, in bytes. 89 * 90 * @param size the initial size. 91 * @exception IllegalArgumentException if size is negative. 92 */ 93 public ByteArrayBuilder(final int size, @Nullable final SizedRecyclingSupplier<byte[]> arraySupplier) { 94 if (size < 0) { 95 throw new IllegalArgumentException("Negative initial size: " 96 + size); 97 } 98 this.arraySupplier = arraySupplier; 99 if (arraySupplier == null) { 100 if (size == 0) { 101 buf = org.spf4j.base.Arrays.EMPTY_BYTE_ARRAY; 102 } else { 103 buf = new byte[size]; 104 } 105 } else { 106 buf = arraySupplier.get(size); 107 } 108 } 109 110 /** 111 * Increases the capacity if necessary to ensure that it can hold at least the number of elements specified by the 112 * minimum capacity argument. 113 * 114 * @param minCapacity the desired minimum capacity 115 * @throws OutOfMemoryError if {@code minCapacity < 0}. This is interpreted as a request for the unsatisfiably large 116 * capacity {@code (long) Integer.MAX_VALUE + (minCapacity - Integer.MAX_VALUE)}. 117 */ 118 private void ensureCapacity(final int minCapacity) { 119 // overflow-conscious code 120 if (minCapacity - buf.length > 0) { 121 grow(minCapacity); 122 } 123 } 124 125 /** 126 * Increases the capacity to ensure that it can hold at least the number of elements specified by the minimum capacity 127 * argument. 128 * 129 * @param minCapacity the desired minimum capacity 130 */ 131 private void grow(final int minCapacity) { 132 // overflow-conscious code 133 int oldCapacity = buf.length; 134 int newCapacity = oldCapacity << 1; 135 if (newCapacity - minCapacity < 0) { 136 newCapacity = minCapacity; 137 } 138 if (newCapacity - MAX_ARRAY_SIZE > 0) { 139 newCapacity = hugeCapacity(minCapacity); 140 } 141 if (arraySupplier == null) { 142 buf = Arrays.copyOf(buf, newCapacity); 143 } else { 144 byte[] old = buf; 145 buf = arraySupplier.get(newCapacity); 146 System.arraycopy(old, 0, buf, 0, old.length); 147 arraySupplier.recycle(old); 148 } 149 } 150 151 private static int hugeCapacity(final int minCapacity) { 152 if (minCapacity < 0) { // overflow 153 throw new OutOfMemoryError(); 154 } 155 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; 156 } 157 158 @Override 159 public void write(final byte[] b) { 160 write(b, 0, b.length); 161 } 162 163 /** 164 * Writes the specified byte to this byte array output stream. 165 * 166 * @param b the byte to be written. 167 */ 168 @Override 169 public synchronized void write(final int b) { 170 int cp1 = count + 1; 171 ensureCapacity(cp1); 172 buf[count] = (byte) b; 173 count = cp1; 174 } 175 176 /** 177 * Writes <code>len</code> bytes from the specified byte array starting at offset <code>off</code> to this byte array 178 * output stream. 179 * 180 * @param b the data. 181 * @param off the start offset in the data. 182 * @param len the number of bytes to write. 183 */ 184 @Override 185 public synchronized void write(final byte[] b, final int off, final int len) { 186 if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) - b.length > 0)) { 187 throw new IndexOutOfBoundsException(); 188 } 189 int cpl = count + len; 190 ensureCapacity(cpl); 191 System.arraycopy(b, off, buf, count, len); 192 count = cpl; 193 } 194 195 /** 196 * Writes the complete contents of this byte array output stream to the specified output stream argument, as if by 197 * calling the output stream's write method using <code>out.write(buf, 0, count)</code>. 198 * 199 * @param out the output stream to which to write the data. 200 * @exception IOException if an I/O error occurs. 201 */ 202 public synchronized void writeTo(final OutputStream out) throws IOException { 203 out.write(buf, 0, count); 204 } 205 206 public synchronized void readFrom(final InputStream in) throws IOException { 207 do { 208 ensureCapacity(count + 8192); 209 int nr = in.read(buf, count, 8192); 210 if (nr < 0) { 211 break; 212 } 213 count += nr; 214 } while (true); 215 } 216 217 /** 218 * Resets the <code>count</code> field of this byte array output stream to zero, so that all currently accumulated 219 * output in the output stream is discarded. The output stream can be used again, reusing the already allocated buffer 220 * space. 221 * 222 * @see java.io.ByteArrayInputStream#count 223 */ 224 public synchronized void reset() { 225 count = 0; 226 } 227 228 public synchronized void resetCountTo(final int pos) { 229 count = pos; 230 } 231 232 /** 233 * Creates a newly allocated byte array. Its size is the current size of this output stream and the valid contents of 234 * the buffer have been copied into it. 235 * 236 * @return the current contents of this output stream, as a byte array. 237 * @see java.io.ByteArrayOutputStream#size() 238 */ 239 public synchronized byte[] toByteArray() { 240 return Arrays.copyOf(buf, count); 241 } 242 243 /** 244 * Returns the current size of the buffer. 245 * 246 * @return the value of the <code>count</code> field, which is the number of valid bytes in this output stream. 247 * @see java.io.ByteArrayOutputStream#count 248 */ 249 public synchronized int size() { 250 return count; 251 } 252 253 /** 254 * Converts the buffer's contents into a string decoding bytes using the platform's default character set. The length 255 * of the new <tt>String</tt> 256 * is a function of the character set, and hence may not be equal to the size of the buffer. 257 * 258 * <p> 259 * This method always replaces malformed-input and unmappable-character sequences with the default replacement string 260 * for the platform's default character set. The {@linkplain java.nio.charset.CharsetDecoder} class should be used 261 * when more control over the decoding process is required. 262 * 263 * @return String decoded from the buffer's contents. 264 */ 265 @Override 266 public synchronized String toString() { 267 return toString(StandardCharsets.UTF_8); 268 } 269 270 public synchronized String toString(final Charset charset) { 271 if (buf == null) { 272 return "Closed ByteArrayBuilder"; 273 } else { 274 return new String(buf, 0, count, charset); 275 } 276 } 277 278 /** 279 * Closing a <tt>ByteArrayOutputStream</tt> will likely recycle the underlying buffer. use of the builder after close 280 * is not advised 281 */ 282 @DischargesObligation 283 @Override 284 public synchronized void close() { 285 if (arraySupplier != null && buf != null) { 286 arraySupplier.recycle(buf); 287 buf = null; 288 } 289 } 290 291 }