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