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.kahadb.util;
018
019import java.io.DataOutput;
020import java.io.IOException;
021import java.io.OutputStream;
022import java.io.UTFDataFormatException;
023
024/**
025 * Optimized ByteArrayOutputStream
026 * 
027 * 
028 */
029public class DataByteArrayOutputStream extends OutputStream implements DataOutput {
030    private static final int DEFAULT_SIZE = 2048;
031    protected byte buf[];
032    protected int pos;
033
034    /**
035     * Creates a new byte array output stream, with a buffer capacity of the
036     * specified size, in bytes.
037     * 
038     * @param size the initial size.
039     * @exception IllegalArgumentException if size is negative.
040     */
041    public DataByteArrayOutputStream(int size) {
042        if (size < 0) {
043            throw new IllegalArgumentException("Invalid size: " + size);
044        }
045        buf = new byte[size];
046    }
047
048    /**
049     * Creates a new byte array output stream.
050     */
051    public DataByteArrayOutputStream() {
052        this(DEFAULT_SIZE);
053    }
054
055    /**
056     * start using a fresh byte array
057     * 
058     * @param size
059     */
060    public void restart(int size) {
061        buf = new byte[size];
062        pos = 0;
063    }
064
065    /**
066     * start using a fresh byte array
067     */
068    public void restart() {
069        restart(DEFAULT_SIZE);
070    }
071
072    /**
073     * Get a ByteSequence from the stream
074     * 
075     * @return the byte sequence
076     */
077    public ByteSequence toByteSequence() {
078        return new ByteSequence(buf, 0, pos);
079    }
080
081    /**
082     * Writes the specified byte to this byte array output stream.
083     * 
084     * @param b the byte to be written.
085     * @throws IOException 
086     */
087    public void write(int b) throws IOException {
088        int newcount = pos + 1;
089        ensureEnoughBuffer(newcount);
090        buf[pos] = (byte)b;
091        pos = newcount;
092        onWrite();
093    }
094
095    /**
096     * Writes <code>len</code> bytes from the specified byte array starting at
097     * offset <code>off</code> to this byte array output stream.
098     * 
099     * @param b the data.
100     * @param off the start offset in the data.
101     * @param len the number of bytes to write.
102     * @throws IOException 
103     */
104    public void write(byte b[], int off, int len) throws IOException {
105        if (len == 0) {
106            return;
107        }
108        int newcount = pos + len;
109        ensureEnoughBuffer(newcount);
110        System.arraycopy(b, off, buf, pos, len);
111        pos = newcount;
112        onWrite();
113    }
114
115    /**
116     * @return the underlying byte[] buffer
117     */
118    public byte[] getData() {
119        return buf;
120    }
121
122    /**
123     * reset the output stream
124     */
125    public void reset() {
126        pos = 0;
127    }
128
129    /**
130     * Set the current position for writing
131     * 
132     * @param offset
133     * @throws IOException 
134     */
135    public void position(int offset) throws IOException {
136        ensureEnoughBuffer(offset);
137        pos = offset;
138        onWrite();
139    }
140
141    public int size() {
142        return pos;
143    }
144
145    public void writeBoolean(boolean v) throws IOException {
146        ensureEnoughBuffer(pos + 1);
147        buf[pos++] = (byte)(v ? 1 : 0);
148        onWrite();
149    }
150
151    public void writeByte(int v) throws IOException {
152        ensureEnoughBuffer(pos + 1);
153        buf[pos++] = (byte)(v >>> 0);
154        onWrite();
155    }
156
157    public void writeShort(int v) throws IOException {
158        ensureEnoughBuffer(pos + 2);
159        buf[pos++] = (byte)(v >>> 8);
160        buf[pos++] = (byte)(v >>> 0);
161        onWrite();
162    }
163
164    public void writeChar(int v) throws IOException {
165        ensureEnoughBuffer(pos + 2);
166        buf[pos++] = (byte)(v >>> 8);
167        buf[pos++] = (byte)(v >>> 0);
168        onWrite();
169    }
170
171    public void writeInt(int v) throws IOException {
172        ensureEnoughBuffer(pos + 4);
173        buf[pos++] = (byte)(v >>> 24);
174        buf[pos++] = (byte)(v >>> 16);
175        buf[pos++] = (byte)(v >>> 8);
176        buf[pos++] = (byte)(v >>> 0);
177        onWrite();
178    }
179
180    public void writeLong(long v) throws IOException {
181        ensureEnoughBuffer(pos + 8);
182        buf[pos++] = (byte)(v >>> 56);
183        buf[pos++] = (byte)(v >>> 48);
184        buf[pos++] = (byte)(v >>> 40);
185        buf[pos++] = (byte)(v >>> 32);
186        buf[pos++] = (byte)(v >>> 24);
187        buf[pos++] = (byte)(v >>> 16);
188        buf[pos++] = (byte)(v >>> 8);
189        buf[pos++] = (byte)(v >>> 0);
190        onWrite();
191    }
192
193    public void writeFloat(float v) throws IOException {
194        writeInt(Float.floatToIntBits(v));
195    }
196
197    public void writeDouble(double v) throws IOException {
198        writeLong(Double.doubleToLongBits(v));
199    }
200
201    public void writeBytes(String s) throws IOException {
202        int length = s.length();
203        for (int i = 0; i < length; i++) {
204            write((byte)s.charAt(i));
205        }
206    }
207
208    public void writeChars(String s) throws IOException {
209        int length = s.length();
210        for (int i = 0; i < length; i++) {
211            int c = s.charAt(i);
212            write((c >>> 8) & 0xFF);
213            write((c >>> 0) & 0xFF);
214        }
215    }
216
217    public void writeUTF(String str) throws IOException {
218        int strlen = str.length();
219        int encodedsize = 0;
220        int c;
221        for (int i = 0; i < strlen; i++) {
222            c = str.charAt(i);
223            if ((c >= 0x0001) && (c <= 0x007F)) {
224                encodedsize++;
225            } else if (c > 0x07FF) {
226                encodedsize += 3;
227            } else {
228                encodedsize += 2;
229            }
230        }
231        if (encodedsize > 65535) {
232            throw new UTFDataFormatException("encoded string too long: " + encodedsize + " bytes");
233        }
234        ensureEnoughBuffer(pos + encodedsize + 2);
235        writeShort(encodedsize);
236        int i = 0;
237        for (i = 0; i < strlen; i++) {
238            c = str.charAt(i);
239            if (!((c >= 0x0001) && (c <= 0x007F))) {
240                break;
241            }
242            buf[pos++] = (byte)c;
243        }
244        for (; i < strlen; i++) {
245            c = str.charAt(i);
246            if ((c >= 0x0001) && (c <= 0x007F)) {
247                buf[pos++] = (byte)c;
248            } else if (c > 0x07FF) {
249                buf[pos++] = (byte)(0xE0 | ((c >> 12) & 0x0F));
250                buf[pos++] = (byte)(0x80 | ((c >> 6) & 0x3F));
251                buf[pos++] = (byte)(0x80 | ((c >> 0) & 0x3F));
252            } else {
253                buf[pos++] = (byte)(0xC0 | ((c >> 6) & 0x1F));
254                buf[pos++] = (byte)(0x80 | ((c >> 0) & 0x3F));
255            }
256        }
257        onWrite();
258    }
259
260    private void ensureEnoughBuffer(int newcount) {
261        if (newcount > buf.length) {
262            byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
263            System.arraycopy(buf, 0, newbuf, 0, pos);
264            buf = newbuf;
265        }
266    }
267    
268    /**
269     * This method is called after each write to the buffer.  This should allow subclasses 
270     * to take some action based on the writes, for example flushing data to an external system based on size. 
271     */
272    protected void onWrite() throws IOException {
273    }
274
275    public void skip(int size) throws IOException {
276        ensureEnoughBuffer(pos + size);
277        pos+=size;
278        onWrite();
279    }
280
281    public ByteSequence getByteSequence() {
282        return new ByteSequence(buf, 0, pos);
283    }
284}