001 /* 002 * SeekableByteArrayOutputStream 003 * 004 * Copyright (c) 2002 Marco Schmidt. 005 * All rights reserved. 006 */ 007 008 package net.sourceforge.jiu.util; 009 010 import java.io.IOException; 011 import java.io.OutputStream; 012 013 /** 014 * An extension of {@link java.io.OutputStream} that writes data to an internal 015 * byte array, resizing it when necessary. 016 * Similar to {@link java.io.ByteArrayOutputStream}, but also enables seeking and truncating. 017 * 018 * @author Marco Schmidt 019 * @since 0.10.0 020 */ 021 public class SeekableByteArrayOutputStream extends OutputStream 022 { 023 private byte[] buffer; 024 private boolean closed; 025 private int incrementStep; 026 private int offset; 027 private int size; 028 029 /** 030 * Creates a new object of this class, setting initial capacity and increment size 031 * to default values. 032 */ 033 public SeekableByteArrayOutputStream() 034 { 035 this(1024, 1024); 036 } 037 038 /** 039 * Creates a new object of this class, setting initial capacity to the argument 040 * value. 041 * The increment size is set to the initial capacity as well if that value is larger 042 * than 0. 043 * Otherwise it is set to a default value. 044 * @param initialCapacity the number of bytes that are allocated in this constructor (0 or larger) 045 */ 046 public SeekableByteArrayOutputStream(int initialCapacity) 047 { 048 this(initialCapacity, initialCapacity == 0 ? 1024 : initialCapacity); 049 } 050 051 /** 052 * Creates a new object of this class, setting initial capacity and increment 053 * to the argument values. 054 * @param initialCapacity the number of bytes that are allocated in this constructor (0 or larger) 055 * @param increment the number of bytes by which the internal byte array is increased if it is full (1 or larger) 056 */ 057 public SeekableByteArrayOutputStream(int initialCapacity, int increment) 058 { 059 if (initialCapacity < 0) 060 { 061 throw new IllegalArgumentException("Value for initial capacity must not be negative."); 062 } 063 if (increment < 1) 064 { 065 throw new IllegalArgumentException("Value for increment must be 1 or larger."); 066 } 067 buffer = new byte[initialCapacity]; 068 incrementStep = increment; 069 offset = 0; 070 } 071 072 /** 073 * Closes this output stream. 074 * After a call to this method, all write attempts will result in an exception. 075 */ 076 public void close() throws IOException 077 { 078 closed = true; 079 } 080 081 private void ensureSpace(int numBytes) throws IOException 082 { 083 if (closed) 084 { 085 throw new IOException("Stream was closed already. Cannot write to closed stream."); 086 } 087 if (numBytes < 0) 088 { 089 throw new IllegalArgumentException("Cannot write negative number of bytes (" + numBytes + ")."); 090 } 091 if (buffer.length - offset < numBytes) 092 { 093 increaseBuffer(Math.max(buffer.length + incrementStep, offset + numBytes)); 094 } 095 } 096 097 /** 098 * Returns the current offset in the output stream. 099 * Larger than or equal to 0 and smaller than or equal to {@link #getSize}. 100 * @return current position in the output stream, 0-based 101 */ 102 public int getPosition() 103 { 104 return offset; 105 } 106 107 /** 108 * Returns the current size of the output stream. 109 * @return size of the output stream in bytes (0 or larger) 110 */ 111 public int getSize() 112 { 113 return size; 114 } 115 116 private void increaseBuffer(int newLength) 117 { 118 if (newLength <= buffer.length) 119 { 120 return; 121 } 122 byte[] temp = new byte[newLength]; 123 System.arraycopy(buffer, 0, temp, 0, offset); 124 buffer = temp; 125 } 126 127 /** 128 * Sets the current position in the output stream to the argument. 129 * @param newOffset new offset into the file, must be >= 0 and <= {@link #getSize} 130 * @throws IOException if the argument is invalid 131 */ 132 public void seek(int newOffset) throws IOException 133 { 134 if (newOffset < 0) 135 { 136 throw new IOException("Cannot seek to negative offset (" + newOffset + ")."); 137 } 138 if (newOffset > size) 139 { 140 throw new IOException("Cannot seek to offset " + newOffset + ", stream has only " + size + " byte(s)."); 141 } 142 offset = newOffset; 143 } 144 145 /** 146 * Allocates a new <code>byte[]</code> object, copies {@link #getSize} bytes 147 * from the internal byte array to that new array and returns the array. 148 * @return a copy of the byte[] data stored internally 149 */ 150 public byte[] toByteArray() 151 { 152 byte[] result = new byte[size]; 153 System.arraycopy(buffer, 0, result, 0, size); 154 return result; 155 } 156 157 /** 158 * Removes all bytes after the current position. 159 * After a call to this method, {@link #getSize} is equal to {@link #getPosition}. 160 */ 161 public void truncate() 162 { 163 size = offset; 164 } 165 166 /** 167 * Writes the least significant eight bits of the argument <code>int</code> to the internal array. 168 * @param b int variable that stores the byte value to be written 169 */ 170 public void write(int b) throws IOException 171 { 172 ensureSpace(1); 173 buffer[offset++] = (byte)b; 174 if (offset > size) 175 { 176 size = offset; 177 } 178 } 179 180 /** 181 * Write the complete argument array to this stream. 182 * Copies the data to the internal byte array. 183 * Simply calls <code>write(data, 0, data.length);</code>. 184 * @param data array to be copied to this stream 185 */ 186 public void write(byte[] data) throws IOException 187 { 188 write(data, 0, data.length); 189 } 190 191 /** 192 * Write some bytes from the argument array to this stream. 193 * Copies num bytes starting at src[srcOffset] to this stream. 194 * @param src the array from which data is copied 195 * @param srcOffset int index into that array pointing to the first byte to be copied 196 * @param num number of bytes to be copied 197 */ 198 public void write(byte[] src, int srcOffset, int num) throws IOException 199 { 200 ensureSpace(num); 201 System.arraycopy(src, srcOffset, buffer, offset, num); 202 offset += num; 203 if (offset > size) 204 { 205 size = offset; 206 } 207 } 208 209 /** 210 * Writes the bytes in the internal byte array to the argument output stream. 211 * A call to this method has the same effect as 212 * <pre> 213 * byte[] copy = toByteArray(); 214 * out.write(copy, 0, copy.length); 215 * </pre> 216 * However, you with this method you save the allocation of an additional byte array 217 * and the copying to that new array. 218 * @param out the output stream to which this stream's content is copied 219 * @throws IOException if out has a problem writing the bytes 220 */ 221 public void writeTo(OutputStream out) throws IOException 222 { 223 out.write(buffer, 0, size); 224 } 225 }