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