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     */
017    package org.apache.kahadb.util;
018    
019    import java.io.DataOutput;
020    import java.io.IOException;
021    import java.io.OutputStream;
022    import java.io.UTFDataFormatException;
023    
024    /**
025     * Optimized ByteArrayOutputStream
026     * 
027     * 
028     */
029    public 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    }