001    /*
002     * This file is part of muCommander, http://www.mucommander.com
003     * Copyright (C) 2002-2008 Maxence Bernard
004     *
005     * muCommander is free software; you can redistribute it and/or modify
006     * it under the terms of the GNU General Public License as published by
007     * the Free Software Foundation; either version 3 of the License, or
008     * (at your option) any later version.
009     *
010     * muCommander is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    
019    package com.mucommander.io;
020    
021    import java.io.IOException;
022    
023    /**
024     * BufferedRandomOutputStream is a buffered output stream for {@link RandomAccessOutputStream} which, unlike a regular
025     * <code>java.io.BufferedOutputStream</code>, makes it safe to seek in the underlying <code>RandomAccessOutputStream</code>.
026     *
027     * <p>This class uses {@link BufferPool} to create the internal buffer, to avoid excessive memory allocation and
028     * garbage collection. The buffer is released when this stream is closed.</p>
029     *
030     * @author Maxence Bernard
031     */
032    public class BufferedRandomOutputStream extends RandomAccessOutputStream {
033    
034        /** The underlying random access output stream */
035        private RandomAccessOutputStream raos;
036    
037        /** The buffer where written bytes are accumulated before being sent to the underlying output stream */
038        private byte buffer[];
039    
040        /** The current number of bytes waiting to be flushed to the underlying output stream */
041        private int count;
042    
043        /** The default buffer size if none is specified */
044        public final static int DEFAULT_BUFFER_SIZE = 65536;
045    
046    
047        /**
048         * Creates a new <code>BufferedRandomOutputStream</code> on top of the given {@link RandomAccessOutputStream}.
049         * An internal buffer of {@link #DEFAULT_BUFFER_SIZE} bytes is created.
050         *
051         * @param raos the underlying RandomAccessOutputStream used by this buffered output stream
052         */
053        public BufferedRandomOutputStream(RandomAccessOutputStream raos) {
054            this(raos, DEFAULT_BUFFER_SIZE);
055        }
056    
057        /**
058         * Creates a new <code>BufferedRandomOutputStream</code> on top of the given {@link RandomAccessOutputStream}.
059         * An internal buffer of the specified size is created.
060         *
061         * @param raos the underlying RandomAccessOutputStream used by this buffered output stream
062         * @param size size of the buffer in bytes
063         */
064        public BufferedRandomOutputStream(RandomAccessOutputStream raos, int size) {
065            this.raos = raos;
066            this.buffer = BufferPool.getByteArray(size);
067        }
068    
069        /**
070         * Flushes the internal buffer.
071         *
072         * @throws IOException if an error occurs
073         */
074        private void flushBuffer() throws IOException {
075            if (count > 0) {
076                raos.write(buffer, 0, count);
077                count = 0;
078            }
079        }
080    
081    
082        /////////////////////////////////////////////
083        // RandomAccessOutputStream implementation //
084        /////////////////////////////////////////////
085    
086        /**
087         * Writes the specified byte to this buffered output stream.
088         *
089         * @param b the byte to be written
090         * @throws IOException if an I/O error occurs
091         */
092        public synchronized void write(int b) throws IOException {
093            if (count >= buffer.length)
094                flushBuffer();
095    
096            buffer[count++] = (byte)b;
097        }
098    
099        /**
100         * Writes the specified byte array to this buffered output stream.
101         *
102         * @param b the bytes to be written
103         * @throws IOException if an I/O error occurs
104         */
105        public synchronized void write(byte b[]) throws IOException {
106            write(b, 0, b.length);
107        }
108    
109        /**
110         * Writes <code>len</code> bytes from the specified byte array starting at offset <code>off</code> to this
111         * buffered output stream.
112         *
113         * <p>Usually this method stores bytes from the given array into this
114         * stream's buffer, flushing the buffer to the underlying output stream as
115         * needed. However, if the requested data length is equal or larger than this stream's
116         * buffer, then this method will flush the buffer and write the
117         * bytes directly to the underlying output stream. Thus redundant
118         * <code>RandomBufferedOutputStream</code>s will not copy data unnecessarily.</p>
119         *
120         * @param b the data.
121         * @param off the start offset in the data.
122         * @param len the number of bytes to write.
123         * @throws IOException if an I/O error occurs.
124         */
125        public synchronized void write(byte b[], int off, int len) throws IOException {
126            if (len >= buffer.length) {
127                /* If the request length exceeds the size of the output buffer,
128                flush the output buffer and then write the data directly.
129                In this way buffered streams will cascade harmlessly. */
130                flushBuffer();
131                raos.write(b, off, len);
132                return;
133            }
134    
135            if (len > buffer.length - count)
136                flushBuffer();
137    
138            System.arraycopy(b, off, buffer, count, len);
139            count += len;
140        }
141    
142        /**
143         * Flushes this buffered output stream. This forces any buffered
144         * output bytes to be written out to the underlying output stream.
145         *
146         * @throws IOException if an I/O error occurs.
147         */
148        public synchronized void flush() throws IOException {
149            flushBuffer();
150            raos.flush();
151        }
152    
153        public synchronized long getOffset() throws IOException {
154            // Add the buffered byte count
155            return raos.getOffset() + count;
156        }
157    
158        public synchronized void seek(long offset) throws IOException {
159            // Flush any buffered bytes before seeking, otherwise buffered bytes would be written at the wrong offset
160            flush();
161    
162            raos.seek(offset);
163        }
164    
165        public synchronized long getLength() throws IOException {
166            // Anticipate if the file is to be expanded by the bytes awaiting in the buffer
167            return Math.max(raos.getLength(), getOffset());
168        }
169    
170        public synchronized void setLength(long newLength) throws IOException {
171            // Flush before changing the file's length, otherwise the behavior of setLength() would be modified, especially
172            // when truncating the file
173            flush();
174    
175            raos.setLength(newLength);
176        }
177    
178    
179        ////////////////////////
180        // Overridden methods //
181        ////////////////////////
182    
183        /**
184         * This method is overridden to release the internal buffer when this stream is closed.
185         */
186        public synchronized void close() throws IOException {
187            if(buffer!=null) {      // buffer is null if close() was already called
188                try {
189                    flush();
190                }
191                catch(IOException e) {
192                    // Continue anyway
193                }
194    
195                // Release the buffer
196                BufferPool.releaseByteArray(buffer);
197                buffer = null;
198            }
199    
200            raos.close();
201        }
202    
203        /**
204         * This method is overridden to release the internal buffer if {@link #close()} has not been called, to avoid any
205         * memory leak.
206         */
207        protected void finalize() throws Throwable {
208            // If this stream hasn't been closed, release the buffer before finalizing the object
209            if(buffer!=null)
210                BufferPool.releaseByteArray(buffer);
211    
212            super.finalize();
213        }
214    }