001    /*
002     * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
003     *
004     * http://uio.imagero.com
005     *
006     * Redistribution and use in source and binary forms, with or without
007     * modification, are permitted provided that the following conditions are met:
008     *
009     *  o Redistributions of source code must retain the above copyright notice,
010     *    this list of conditions and the following disclaimer.
011     *
012     *  o Redistributions in binary form must reproduce the above copyright notice,
013     *    this list of conditions and the following disclaimer in the documentation
014     *    and/or other materials provided with the distribution.
015     *
016     *  o Neither the name of Andrey Kuznetsov nor the names of
017     *    its contributors may be used to endorse or promote products derived
018     *    from this software without specific prior written permission.
019     *
020     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
021     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
022     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
023     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
027     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
028     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
029     * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
030     * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031     */
032    package com.imagero.uio.io;
033    
034    import java.awt.event.ActionEvent;
035    import java.awt.event.ActionListener;
036    import java.io.ByteArrayOutputStream;
037    import java.io.DataOutput;
038    import java.io.IOException;
039    
040    /**
041     * ByteArrayOutputStreamExt extends ByteArrayOutputStream with posibility to drain off data into user specified buffer.
042     * ActionEvent is fired if buffer is full and going to grow.
043     * @author Andrey Kuznetsov
044     */
045    public class ByteArrayOutputStreamExt extends ByteArrayOutputStream {
046    
047        public static final String BUFFER_FULL = "buffer full";
048    
049        ActionListener bufferListener;
050    
051        public ByteArrayOutputStreamExt() {
052            this(1024);
053        }
054    
055        public ByteArrayOutputStreamExt(int size) {
056            super(size);
057        }
058    
059        public ByteArrayOutputStreamExt(ActionListener l) {
060            this.bufferListener = l;
061        }
062    
063        public ByteArrayOutputStreamExt(int size, ActionListener l) {
064            super(size);
065            this.bufferListener = l;
066        }
067    
068        protected void fireBufferFullEvent() {
069            drained = false;
070            if (bufferListener != null) {
071                ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, BUFFER_FULL);
072                bufferListener.actionPerformed(e);
073            }
074        }
075    
076        /**
077         * Fill destination buffer with data which is removed from start of this buffer.
078         * @param dest destination buffer
079         * @return how much bytes was moved from this buffer into destination buffer.
080         */
081        public synchronized int drain(byte[] dest) {
082            int length = Math.min(dest.length, count);
083            if (length > 0) {
084                System.arraycopy(buf, 0, dest, 0, length);
085                int len = count - length;
086                if (len > 0) {
087                    System.arraycopy(buf, length, buf, 0, len);
088                }
089                count -= length;
090            }
091            drained = true;
092            return length;
093        }
094    
095        boolean drained;
096    
097        public synchronized void write(int b) {
098            if (bufferListener != null) {
099                int newcount = count + 1;
100                if (newcount > buf.length) {
101                    fireBufferFullEvent();
102                }
103            }
104            super.write(b);
105        }
106    
107        /**
108         * Writes len bytes from given byte array starting at offset off to this buffer.
109         * If capacity of buffer is not enough for incoming data,
110         * then, at-first, buffer filled with data,
111         * then fired "buffer is full" event,
112         * thus giving the user possibility to "drain" buffer.
113         * If buffer was drained, then rest of data is written to buffer,
114         * otherwise buffer capacity is increased before writing.
115         * @param b
116         * @param off
117         * @param len
118         */
119        public synchronized void write(byte b[], int off, int len) {
120            int max = buf.length - count;
121            if (max > len || bufferListener == null) {
122                super.write(b, off, len);
123            } else {
124                super.write(b, off, max);
125                fireBufferFullEvent();
126                write2(b, off + max, len - max);
127            }
128        }
129    
130        private void write2(byte b[], int off, int len) {
131            if (!drained) {
132                super.write(b, off, len);
133            } else {
134                drained = false;
135                int max = buf.length - count;
136                if (max > len) {
137                    super.write(b, off, len);
138                } else {
139                    super.write(b, off, max);
140                    fireBufferFullEvent();
141                    write2(b, off + max, len - max);
142                }
143            }
144        }
145    
146        public void close() throws IOException {
147            fireBufferFullEvent();
148            super.close();
149        }
150    
151        public void setBufferListener(ActionListener bufferListener) {
152            this.bufferListener = bufferListener;
153        }
154    
155        /**
156         * Retrieve internal buffer.
157         * Recommended use - immediately after receiving "buffer full" event.
158         * Internal buffer is returned and replaced with new empty buffer.
159         */
160        public synchronized byte[] drain() {
161            byte[] tmp = buf;
162            buf = new byte[0];
163            count = 0;
164            drained = true;
165            return tmp;
166        }
167    
168        public synchronized void writeTo(DataOutput out) throws IOException {
169            out.write(buf, 0, count);
170        }
171    
172        public byte [] closeStream() {
173            byte [] tmp = buf;
174            buf = null;
175            return tmp;
176        }
177    }